/* src/interfaces/ecpg/pgtypeslib/dt_common.c */

#include "postgres_fe.h"

#include <time.h>
#include <ctype.h>
#include <math.h>

#include "common/string.h"
#include "dt.h"
#include "pgtypes_timestamp.h"
#include "pgtypeslib_extern.h"

const int	day_tab[2][13] = {
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}};

typedef long AbsoluteTime;

static const datetkn datetktbl[] = {
/* 文本，标记，词法值 */  
	{EARLY, RESERV, DTK_EARLY}, /* "-infinity" 预留用于 "早期时间" */  
	{"acsst", DTZ, 37800},		/* 澳大利亚中部 */  
	{"acst", DTZ, -14400},		/* 大西洋/波尔图阿克雷 */  
	{"act", TZ, -18000},		/* 大西洋/波尔图阿克雷 */  
	{DA_D, ADBC, AD},			/* "ad" 表示年份 >= 0 */  
	{"adt", DTZ, -10800},		/* 大西洋夏令时 */  
	{"aesst", DTZ, 39600},		/* 东澳大利亚 */  
	{"aest", TZ, 36000},		/* 澳大利亚东部标准时间 */  
	{"aft", TZ, 16200},			/* 喀布尔 */  
	{"ahst", TZ, -36000},		/* 阿拉斯加-夏威夷标准时间 */  
	{"akdt", DTZ, -28800},		/* 阿拉斯加夏令时 */  
	{"akst", DTZ, -32400},		/* 阿拉斯加标准时间 */  
	{"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */
	{"almst", TZ, 25200},		/* 阿拉木图节能时间 */  
	{"almt", TZ, 21600},		/* 阿拉木图时间 */  
	{"am", AMPM, AM},
	{"amst", DTZ, 18000},		/* 亚美尼亚夏令时（耶烈万） */  
#if 0
	{"amst", DTZ, -10800},		/* 波尔图韦柳 */  
#endif
	{"amt", TZ, 14400},			/* 亚美尼亚时间（耶烈万） */  
	{"anast", DTZ, 46800},		/* 阿纳德尔夏令时（俄罗斯） */  
	{"anat", TZ, 43200},		/* 阿纳德尔时间（俄罗斯） */  
	{"apr", MONTH, 4},
	{"april", MONTH, 4},
#if 0
	aqtst
	aqtt
	arst
#endif
	{"art", TZ, -10800},		/* 阿根廷时间 */  
#if 0
	ashst
	ast							/* 大西洋标准时间，阿拉伯标准时间，阿克雷标准时间 */  
#endif
	{"ast", TZ, -14400},		/* 大西洋标准时间（加拿大） */  
	{"at", IGNORE_DTF, 0},		/* "at"（丢弃） */  
	{"aug", MONTH, 8},
	{"august", MONTH, 8},
	{"awsst", DTZ, 32400},		/* 西澳大利亚 */  
	{"awst", TZ, 28800},		/* 西澳大利亚 */  
	{"awt", DTZ, -10800},
	{"azost", DTZ, 0},			/* 亚速尔群岛夏令时 */  
	{"azot", TZ, -3600},		/* 亚速尔群岛时间 */  
	{"azst", DTZ, 18000},		/* 阿塞拜疆夏令时 */  
	{"azt", TZ, 14400},			/* 阿塞拜疆时间 */  
	{DB_C, ADBC, BC},			/* "bc" 表示年份 < 0 */  
	{"bdst", TZ, 7200},			/* 英国双夏令时 */  
	{"bdt", TZ, 21600},			/* 达卡 */  
	{"bnt", TZ, 28800},			/* 文莱达鲁萨兰时间 */  
	{"bort", TZ, 28800},		/* 加里曼丹时间（印度尼西亚） */  
#if 0
	bortst
	bost
#endif
	{"bot", TZ, -14400},		/* 玻利维亚时间 */  
	{"bra", TZ, -10800},		/* 巴西时间 */  
#if 0
	brst
	brt
#endif
	{"bst", DTZ, 3600},			/* 英国夏令时 */  
#if 0
	{"bst", TZ, -10800},		/* 巴西标准时间 */  
	{"bst", DTZ, -39600},		/* 白令海夏令时 */  
#endif
	{"bt", TZ, 10800},			/* 巴格达时间 */  
	{"btt", TZ, 21600},			/* 不丹时间 */  
	{"cadt", DTZ, 37800},		/* 中澳大利亚夏令时 */  
	{"cast", TZ, 34200},		/* 中澳大利亚标准时间 */  
	{"cat", TZ, -36000},		/* 中阿拉斯加时间 */  
	{"cct", TZ, 28800},			/* 中国沿海时间 */  
#if 0
	{"cct", TZ, 23400},			/* 印度科科斯（岛屿）时间 */  
#endif
	{"cdt", DTZ, -18000},		/* 中部夏令时 */  
	{"cest", DTZ, 7200},		/* 中欧夏令时 */  
	{"cet", TZ, 3600},			/* 中欧时间 */  
	{"cetdst", DTZ, 7200},		/* 中欧夏令时 */  
	{"chadt", DTZ, 49500},		/* 查塔姆岛夏令时（13:45） */  
	{"chast", TZ, 45900},		/* 查塔姆岛时间（12:45） */  
#if 0
	ckhst
#endif
	{"ckt", TZ, 43200},			/* 库克群岛时间 */  
	{"clst", DTZ, -10800},		/* 智利夏令时 */  
	{"clt", TZ, -14400},		/* 智利时间 */  
#if 0
	cost
#endif
	{"cot", TZ, -18000},		/* 哥伦比亚时间 */  
	{"cst", TZ, -21600},		/* 中部标准时间 */  
#if 0
	cvst
#endif
	{"cvt", TZ, 25200},			/* 圣诞岛时间（印度洋） */  
	{"cxt", TZ, 25200},			/* 圣诞岛时间（印度洋） */  
	{"d", UNITS, DTK_DAY},		/* "月份的天" 用于 ISO 输入 */  
	{"davt", TZ, 25200},		/* 戴维斯时间（南极洲） */  
	{"ddut", TZ, 36000},		/* 杜蒙特-杜尔维尔时间（南极洲） */  
	{"dec", MONTH, 12},
	{"december", MONTH, 12},
	{"dnt", TZ, 3600},			/* 丹麦标准时间 */  
	{"dow", UNITS, DTK_DOW},	/* 周中的天 */  
	{"doy", UNITS, DTK_DOY},	/* 年中的天 */  
	{"dst", DTZMOD, SECS_PER_HOUR},
#if 0
	{"dusst", DTZ, 21600},		/* 杜尚别夏令时 */  
#endif
	{"easst", DTZ, -18000},		/* 复活节岛夏令时 */  
	{"east", TZ, -21600},		/* 复活节岛时间 */  
	{"eat", TZ, 10800},			/* 东非时间 */  
#if 0
	{"east", DTZ, 14400},		/* 印度安塔那那利佛节省时间 */  
	{"eat", TZ, 10800},			/* 印度安塔那那利佛时间 */  
	{"ect", TZ, -14400},		/* 东加勒比时间 */  
	{"ect", TZ, -18000},		/* 厄瓜多尔时间 */  
#endif
	{"edt", DTZ, -14400},		/* 东部夏令时 */  
	{"eest", DTZ, 10800},		/* 东欧夏令时 */  
	{"eet", TZ, 7200},			/* 东欧，苏联区 1 */  
	{"eetdst", DTZ, 10800},		/* 东欧夏令时 */  
	{"egst", DTZ, 0},			/* 东格林兰夏令时 */  
	{"egt", TZ, -3600},			/* 东格林兰时间 */  
#if 0
	ehdt
#endif
	{EPOCH, RESERV, DTK_EPOCH}, /* "纪元" 预留用于系统纪元时间 */  
	{"est", TZ, -18000},		/* 东部标准时间 */  
	{"feb", MONTH, 2},
	{"february", MONTH, 2},
	{"fjst", DTZ, -46800},		/* 斐济夏令时（13小时偏移！） */  
	{"fjt", TZ, -43200},		/* 斐济时间 */  
	{"fkst", DTZ, -10800},		/* 福克兰群岛夏令时 */  
	{"fkt", TZ, -7200},			/* 福克兰群岛时间 */  
#if 0
	fnst
	fnt
#endif
	{"fri", DOW, 5},
	{"friday", DOW, 5},
	{"fst", TZ, 3600},			/* 法国夏令时 */  
	{"fwt", DTZ, 7200},			/* 法国冬令时 */  
	{"galt", TZ, -21600},		/* 加拉帕戈斯时间 */  
	{"gamt", TZ, -32400},		/* 甘比尔时间 */
	{"gest", DTZ, 18000},		/* 乔治亚夏令时间 */
	{"get", TZ, 14400},			/* 乔治亚时间 */
	{"gft", TZ, -10800},		/* 法属圭亚那时间 */
#if 0
	ghst
#endif
	{"gilt", TZ, 43200},		/* 吉尔伯特群岛时间 */
	{"gmt", TZ, 0},				/* 格林威治标准时间 */
	{"gst", TZ, 36000},			/* 关岛标准时间，苏联第9区 */
	{"gyt", TZ, -14400},		/* 圭亚那时间 */
	{"h", UNITS, DTK_HOUR},		/* "小时" */
#if 0
	hadt
	hast
#endif
	{"hdt", DTZ, -32400},		/* 夏威夷/阿拉斯加夏令时间 */
#if 0
	hkst
#endif
	{"hkt", TZ, 28800},			/* 香港时间 */
#if 0
	{"hmt", TZ, 10800},			/* 希腊 ? ? */
	hovst
	hovt
#endif
	{"hst", TZ, -36000},		/* 夏威夷标准时间 */
#if 0
	hwt
#endif
	{"ict", TZ, 25200},			/* 法属印度支那时间 */
	{"idle", TZ, 43200},		/* 国际日期变更线，东 */
	{"idlw", TZ, -43200},		/* 国际日期变更线，西 */
#if 0
	idt							/* 以色列，伊朗，印度夏令时间 */
#endif
	{LATE, RESERV, DTK_LATE},	/* 为“晚时间”保留的“无穷大” */
	{"iot", TZ, 18000},			/* 印度查戈斯时间 */
	{"irkst", DTZ, 32400},		/* 伊尔库茨克夏令时间 */
	{"irkt", TZ, 28800},		/* 伊尔库茨克时间 */
	{"irt", TZ, 12600},			/* 伊朗时间 */
	{"isodow", UNITS, DTK_ISODOW},	/* ISO周几，星期天 == 7 */
#if 0
	isst
#endif
	{"ist", TZ, 7200},			/* 以色列 */
	{"it", TZ, 12600},			/* 伊朗时间 */
	{"j", UNITS, DTK_JULIAN},
	{"jan", MONTH, 1},
	{"january", MONTH, 1},
	{"javt", TZ, 25200},		/* 爪哇时间（07:00？见JT） */
	{"jayt", TZ, 32400},		/* 贾亚普拉时间（印度尼西亚） */
	{"jd", UNITS, DTK_JULIAN},
	{"jst", TZ, 32400},			/* 日本标准时间，苏联第8区 */
	{"jt", TZ, 27000},			/* 爪哇时间（07:30？见JAVT） */
	{"jul", MONTH, 7},
	{"julian", UNITS, DTK_JULIAN},
	{"july", MONTH, 7},
	{"jun", MONTH, 6},
	{"june", MONTH, 6},
	{"kdt", DTZ, 36000},		/* 韩国夏令时间 */
	{"kgst", DTZ, 21600},		/* 吉尔吉斯斯坦夏令时间 */
	{"kgt", TZ, 18000},			/* 吉尔吉斯斯坦时间 */
	{"kost", TZ, 43200},		/* 科斯拉时间 */
	{"krast", DTZ, 25200},		/* 克拉斯诺亚尔斯克夏令时间 */
	{"krat", TZ, 28800},		/* 克拉斯诺亚尔斯克标准时间 */
	{"kst", TZ, 32400},			/* 韩国标准时间 */
	{"lhdt", DTZ, 39600},		/* 豪勋爵岛夏令时间，澳大利亚 */
	{"lhst", TZ, 37800},		/* 豪勋爵岛标准时间，澳大利亚 */
	{"ligt", TZ, 36000},		/* 来自澳大利亚墨尔本 */
	{"lint", TZ, 50400},		/* 线岛时间（基里巴斯；+14小时！） */
	{"lkt", TZ, 21600},			/* 斯里兰卡时间 */
	{"m", UNITS, DTK_MONTH},	/* 用于ISO输入的“月份” */
	{"magst", DTZ, 43200},		/* Магадан夏令时间 */
	{"magt", TZ, 39600},		/* Магадан时间 */
	{"mar", MONTH, 3},
	{"march", MONTH, 3},
	{"mart", TZ, -34200},		/* 马克萨斯时间 */
	{"mawt", TZ, 21600},		/* 毛森，南极洲 */
	{"may", MONTH, 5},
	{"mdt", DTZ, -21600},		/* 山区夏令时间 */
	{"mest", DTZ, 7200},		/* 中欧夏令时间 */
	{"met", TZ, 3600},			/* 中欧时间 */
	{"metdst", DTZ, 7200},		/* 中欧夏令时间 */
	{"mewt", TZ, 3600},			/* 中欧冬令时间 */
	{"mez", TZ, 3600},			/* 中欧区域 */
	{"mht", TZ, 43200},			/* 库瓦贾莱因 */
	{"mm", UNITS, DTK_MINUTE},	/* 用于ISO输入的“分钟” */
	{"mmt", TZ, 23400},			/* 缅甸时间 */
	{"mon", DOW, 1},
	{"monday", DOW, 1},
#if 0
	most
#endif
	{"mpt", TZ, 36000},			/* 北马里亚纳群岛时间 */
	{"msd", DTZ, 14400},		/* 俄罗斯莫斯科夏令时间 */
	{"msk", TZ, 10800},			/* 俄罗斯莫斯科时间 */
	{"mst", TZ, -25200},		/* 山区标准时间 */
	{"mt", TZ, 30600},			/* 梅拉尼西亚时间 */
	{"mut", TZ, 14400},			/* 毛里求斯岛时间 */
	{"mvt", TZ, 18000},			/* 马尔代夫岛时间 */
	{"myt", TZ, 28800},			/* 马来西亚时间 */
#if 0
	ncst
#endif
	{"nct", TZ, 39600},			/* 新喀里多尼亚时间 */
	{"ndt", DTZ, -9000},		/* 纽芬兰夏令时间 */
	{"nft", TZ, -12600},		/* 纽芬兰标准时间 */
	{"nor", TZ, 3600},			/* 挪威标准时间 */
	{"nov", MONTH, 11},
	{"november", MONTH, 11},
	{"novst", DTZ, 25200},		/* 新西伯利亚夏令时间 */
	{"novt", TZ, 21600},		/* 新西伯利亚标准时间 */
	{NOW, RESERV, DTK_NOW},		/* 当前交易时间 */
	{"npt", TZ, 20700},			/* 尼泊尔标准时间（GMT-5:45） */
	{"nst", TZ, -12600},		/* 纽芬兰标准时间 */
	{"nt", TZ, -39600},			/* 诺姆时间 */
	{"nut", TZ, -39600},		/* 纽埃时间 */
	{"nzdt", DTZ, 46800},		/* 新西兰夏令时间 */
	{"nzst", TZ, 43200},		/* 新西兰标准时间 */
	{"nzt", TZ, 43200},			/* 新西兰时间 */
	{"oct", MONTH, 10},
	{"october", MONTH, 10},
	{"omsst", DTZ, 25200},		/* 鄂木斯克夏令时间 */
	{"omst", TZ, 21600},		/* 鄂木斯克时间 */
	{"on", IGNORE_DTF, 0},		/* “开” （抛弃） */
	{"pdt", DTZ, -25200},		/* 太平洋夏令时间 */
#if 0
	pest
#endif
	{"pet", TZ, -18000},		/* 秘鲁时间 */
	{"petst", DTZ, 46800},		/* 彼得堡-堪察加夏令时间 */
	{"pett", TZ, 43200},		/* 彼得堡-堪察加时间 */
	{"pgt", TZ, 36000},			/* 巴布亚新几内亚时间 */
	{"phot", TZ, 46800},		/* 凤凰岛（基里巴斯）时间 */
#if 0
	phst
#endif
	{"pht", TZ, 28800},			/* 菲律宾时间 */
	{"pkt", TZ, 18000},			/* 巴基斯坦时间 */
	{"pm", AMPM, PM},
	{"pmdt", DTZ, -7200},		/* 皮埃尔和密克隆夏令时间 */
#if 0
	pmst
#endif
	{"pont", TZ, 39600},		/* 波纳佩时间（密克罗尼西亚） */
	{"pst", TZ, -28800},		/* 太平洋标准时间 */
	{"pwt", TZ, 32400},			/* 帕劳时间 */
	{"pyst", DTZ, -10800},		/* 巴拉圭夏令时间 */
	{"pyt", TZ, -14400},		/* 巴拉圭时间 */
	{"ret", DTZ, 14400},		/* 留尼旺岛时间 */
	{"s", UNITS, DTK_SECOND},	/* "秒" 用于 ISO 输入 */
	{"sadt", DTZ, 37800},		/* 南澳大利亚夏令时 */
#if 0
	samst
	samt
#endif
	{"sast", TZ, 34200},		/* 南澳大利亚标准时间 */
	{"sat", DOW, 6},
	{"saturday", DOW, 6},
#if 0
	sbt
#endif
	{"sct", DTZ, 14400},		/* 马赫岛时间 */
	{"sep", MONTH, 9},
	{"sept", MONTH, 9},
	{"september", MONTH, 9},
	{"set", TZ, -3600},			/* 塞舌尔时间 ?? */
#if 0
	sgt
#endif
	{"sst", DTZ, 7200},			/* 瑞典夏令时 */
	{"sun", DOW, 0},
	{"sunday", DOW, 0},
	{"swt", TZ, 3600},			/* 瑞典冬令时 */
#if 0
	syot
#endif
	{"t", ISOTIME, DTK_TIME},	/* ISO 时间字段的填充 */
	{"tft", TZ, 18000},			/* 凯尔盖伦时间 */
	{"that", TZ, -36000},		/* 塔希提时间 */
	{"thu", DOW, 4},
	{"thur", DOW, 4},
	{"thurs", DOW, 4},
	{"thursday", DOW, 4},
	{"tjt", TZ, 18000},			/* 塔吉克斯坦时间 */
	{"tkt", TZ, -36000},		/* 托克劳时间 */
	{"tmt", TZ, 18000},			/* 土库曼斯坦时间 */
	{TODAY, RESERV, DTK_TODAY}, /* 午夜 */
	{TOMORROW, RESERV, DTK_TOMORROW},	/* 明天午夜 */
#if 0
	tost
#endif
	{"tot", TZ, 46800},			/* 汤加时间 */
#if 0
	tpt
#endif
	{"truk", TZ, 36000},		/* 特鲁克时间 */
	{"tue", DOW, 2},
	{"tues", DOW, 2},
	{"tuesday", DOW, 2},
	{"tvt", TZ, 43200},			/* 图瓦卢时间 */
#if 0
	uct
#endif
	{"ulast", DTZ, 32400},		/* 乌兰巴托夏令时 */
	{"ulat", TZ, 28800},		/* 乌兰巴托时间 */
	{"ut", TZ, 0},
	{"utc", TZ, 0},
	{"uyst", DTZ, -7200},		/* 乌拉圭夏令时 */
	{"uyt", TZ, -10800},		/* 乌拉圭时间 */
	{"uzst", DTZ, 21600},		/* 乌兹别克斯坦夏令时 */
	{"uzt", TZ, 18000},			/* 乌兹别克斯坦时间 */
	{"vet", TZ, -14400},		/* 委内瑞拉时间 */
	{"vlast", DTZ, 39600},		/* 符拉迪沃斯托克夏令时 */
	{"vlat", TZ, 36000},		/* 符拉迪沃斯托克时间 */
#if 0
	vust
#endif
	{"vut", TZ, 39600},			/* 瓦努阿图时间 */
	{"wadt", DTZ, 28800},		/* 西澳大利亚夏令时 */
	{"wakt", TZ, 43200},		/* 韦克时间 */
#if 0
	warst
#endif
	{"wast", TZ, 25200},		/* 西澳大利亚标准时间 */
	{"wat", TZ, -3600},			/* 西非时间 */
	{"wdt", DTZ, 32400},		/* 西澳大利亚夏令时 */
	{"wed", DOW, 3},
	{"wednesday", DOW, 3},
	{"weds", DOW, 3},
	{"west", DTZ, 3600},		/* 西欧夏令时 */
	{"wet", TZ, 0},				/* 西欧洲 */
	{"wetdst", DTZ, 3600},		/* 西欧洲夏令时 */
	{"wft", TZ, 43200},			/* 瓦利斯和富图纳时间 */
	{"wgst", DTZ, -7200},		/* 西格林兰夏令时 */
	{"wgt", TZ, -10800},		/* 西格林兰时间 */
	{"wst", TZ, 28800},			/* 西澳大利亚标准时间 */
	{"y", UNITS, DTK_YEAR},		/* "年" 用于 ISO 输入 */
	{"yakst", DTZ, 36000},		/* 雅库茨克夏令时 */
	{"yakt", TZ, 32400},		/* 雅库茨克时间 */
	{"yapt", TZ, 36000},		/* 雅普时间（密克罗尼西亚） */
	{"ydt", DTZ, -28800},		/* 雪鸦州夏令时 */
	{"yekst", DTZ, 21600},		/* 叶卡捷琳堡夏令时 */
	{"yekt", TZ, 18000},		/* 叶卡捷琳堡时间 */
	{YESTERDAY, RESERV, DTK_YESTERDAY}, /* 昨天午夜 */
	{"yst", TZ, -32400},		/* 雪鸦州标准时间 */
	{"z", TZ, 0},				/* ISO-8601 的时区标签 */
	{"zp4", TZ, -14400},		/* UTC +4 小时。 */
	{"zp5", TZ, -18000},		/* UTC +5 小时。 */
	{"zp6", TZ, -21600},		/* UTC +6 小时。 */
	{ZULU, TZ, 0},				/* UTC */
};

static const datetkn deltatktbl[] = {
	/* 文本，标记，词汇值 */
	{"@", IGNORE_DTF, 0},		/* postgres 相对前缀 */
	{DAGO, AGO, 0},				/* "之前" 表示负时间偏移 */
	{"c", UNITS, DTK_CENTURY},	/* "世纪" 相对 */
	{"cent", UNITS, DTK_CENTURY},	/* "世纪" 相对 */
	{"centuries", UNITS, DTK_CENTURY},	/* "世纪" 相对 */
	{DCENTURY, UNITS, DTK_CENTURY}, /* "世纪" 相对 */
	{"d", UNITS, DTK_DAY},		/* "天" 相对 */
	{DDAY, UNITS, DTK_DAY},		/* "天" 相对 */
	{"days", UNITS, DTK_DAY},	/* "天" 相对 */
	{"dec", UNITS, DTK_DECADE}, /* "十年" 相对 */
	{DDECADE, UNITS, DTK_DECADE},	/* "十年" 相对 */
	{"decades", UNITS, DTK_DECADE}, /* "十年" 相对 */
	{"decs", UNITS, DTK_DECADE},	/* "十年" 相对 */
	{"h", UNITS, DTK_HOUR},		/* "小时" 相对 */
	{DHOUR, UNITS, DTK_HOUR},	/* "小时" 相对 */
	{"hours", UNITS, DTK_HOUR}, /* "小时" 相对 */
	{"hr", UNITS, DTK_HOUR},	/* "小时" 相对 */
	{"hrs", UNITS, DTK_HOUR},	/* "小时" 相对 */
	{"m", UNITS, DTK_MINUTE},	/* "分钟" 相对 */
	{"microsecon", UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{"mil", UNITS, DTK_MILLENNIUM}, /* "千年" 相对 */
	{"millennia", UNITS, DTK_MILLENNIUM},	/* "千年" 相对 */
	{DMILLENNIUM, UNITS, DTK_MILLENNIUM},	/* "千年" 相对 */
	{"millisecon", UNITS, DTK_MILLISEC},	/* 相对 */
	{"mils", UNITS, DTK_MILLENNIUM},	/* "千年" 相对 */
	{"min", UNITS, DTK_MINUTE}, /* "分钟" 相对 */
	{"mins", UNITS, DTK_MINUTE},	/* "分钟" 相对 */
	{DMINUTE, UNITS, DTK_MINUTE},	/* "分钟" 相对 */
	{"minutes", UNITS, DTK_MINUTE}, /* "分钟" 相对 */
	{"mon", UNITS, DTK_MONTH},	/* "月" 相对 */
	{"mons", UNITS, DTK_MONTH}, /* "月" 相对 */
	{DMONTH, UNITS, DTK_MONTH}, /* "月" 相对 */
	{"months", UNITS, DTK_MONTH},
	{"ms", UNITS, DTK_MILLISEC},
	{"msec", UNITS, DTK_MILLISEC},
	{DMILLISEC, UNITS, DTK_MILLISEC},
	{"mseconds", UNITS, DTK_MILLISEC},
	{"msecs", UNITS, DTK_MILLISEC},
	{"qtr", UNITS, DTK_QUARTER},	/* "季度" 相对 */
	{DQUARTER, UNITS, DTK_QUARTER}, /* "季度" 相对 */
	{"s", UNITS, DTK_SECOND},
	{"sec", UNITS, DTK_SECOND},
	{DSECOND, UNITS, DTK_SECOND},
	{"seconds", UNITS, DTK_SECOND},
	{"secs", UNITS, DTK_SECOND},
	{DTIMEZONE, UNITS, DTK_TZ}, /* "时区" 时间偏移 */
	{"timezone_h", UNITS, DTK_TZ_HOUR}, /* 时区小时单位 */
	{"timezone_m", UNITS, DTK_TZ_MINUTE},	/* 时区分钟单位 */
	{"us", UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{"usec", UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{DMICROSEC, UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{"useconds", UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{"usecs", UNITS, DTK_MICROSEC}, /* "微秒" 相对 */
	{"w", UNITS, DTK_WEEK},		/* "周" 相对 */
	{DWEEK, UNITS, DTK_WEEK},	/* "周" 相对 */
	{"weeks", UNITS, DTK_WEEK}, /* "weeks" relative */
	{"y", UNITS, DTK_YEAR},		/* "year" relative */
	{DYEAR, UNITS, DTK_YEAR},	/* "year" relative */
	{"years", UNITS, DTK_YEAR}, /* "years" relative */
	{"yr", UNITS, DTK_YEAR},	/* "year" relative */
	{"yrs", UNITS, DTK_YEAR},	/* "years" relative */
};

static const unsigned int szdatetktbl = lengthof(datetktbl);
static const unsigned int szdeltatktbl = lengthof(deltatktbl);

static const datetkn *datecache[MAXDATEFIELDS] = {NULL};

static const datetkn *deltacache[MAXDATEFIELDS] = {NULL};

char	   *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};

char	   *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", NULL};

char	   *pgtypes_date_weekdays_short[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL};

char	   *pgtypes_date_months[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", NULL};

static const datetkn *
fc_datebsearch(const char *fc_key, const datetkn *fc_base, unsigned int fc_nel)
{
	if (fc_nel > 0)
	{
		const datetkn *fc_last = fc_base + fc_nel - 1,
				   *fc_position;
		int			fc_result;

		while (fc_last >= fc_base)
		{
			fc_position = fc_base + ((fc_last - fc_base) >> 1);
			/* precheck the first character for a bit of extra speed */
			fc_result = (int) fc_key[0] - (int) fc_position->token[0];
			if (fc_result == 0)
			{
				/* use strncmp so that we match truncated tokens */
				fc_result = strncmp(fc_key, fc_position->token, TOKMAXLEN);
				if (fc_result == 0)
					return fc_position;
			}
			if (fc_result < 0)
				fc_last = fc_position - 1;
			else
				fc_base = fc_position + 1;
		}
	}
	return NULL;
}

/*
		 * 我们有一个SQL99的强制时区吗？那么就直接使用它，因为我们已经旋转到该时区。
		 */
int DecodeUnits(int fc_field, char *fc_lowtoken, int *fc_val)
{
	int			fc_type;
	const datetkn *fc_tp;

	/* use strncmp so that we match truncated tokens */
	if (deltacache[fc_field] != NULL &&
		strncmp(fc_lowtoken, deltacache[fc_field]->token, TOKMAXLEN) == 0)
		fc_tp = deltacache[fc_field];
	else
		fc_tp = fc_datebsearch(fc_lowtoken, deltatktbl, szdeltatktbl);
	deltacache[fc_field] = fc_tp;
	if (fc_tp == NULL)
	{
		fc_type = UNKNOWN_FIELD;
		*fc_val = 0;
	}
	else
	{
		fc_type = fc_tp->type;
		*fc_val = fc_tp->value;
	}

	return fc_type;
}								/* DecodeUnits() */

/*
			 * 最多复制MAXTZLEN字节的时区到tzn，以防它包含一个错误消息，该消息无法放入缓冲区
			 */

int date2j(int fc_y, int fc_m, int fc_d)
{
	int			fc_julian;
	int			fc_century;

	if (fc_m > 2)
	{
		fc_m += 1;
		fc_y += 4800;
	}
	else
	{
		fc_m += 13;
		fc_y += 4799;
	}

	fc_century = fc_y / 100;
	fc_julian = fc_y * 365 - 32167;
	fc_julian += fc_y / 4 - fc_century + fc_century / 4;
	fc_julian += 7834 * fc_m / 256 + fc_d;

	return fc_julian;
}								/* date2j() */

void j2date(int fc_jd, int *fc_year, int *fc_month, int *fc_day)
{
	unsigned int fc_julian;
	unsigned int fc_quad;
	unsigned int fc_extra;
	int			fc_y;

	fc_julian = fc_jd;
	fc_julian += 32044;
	fc_quad = fc_julian / 146097;
	fc_extra = (fc_julian - fc_quad * 146097) * 4 + 3;
	fc_julian += 60 + fc_quad * 3 + fc_extra / 146097;
	fc_quad = fc_julian / 1461;
	fc_julian -= fc_quad * 1461;
	fc_y = fc_julian * 4 / 1461;
	fc_julian = ((fc_y != 0) ? (fc_julian + 305) % 365 : (fc_julian + 306) % 366) + 123;
	fc_y += fc_quad * 4;
	*fc_year = fc_y - 4800;
	fc_quad = fc_julian * 2141 / 65536;
	*fc_day = fc_julian - 7834 * fc_quad / 256;
	*fc_month = (fc_quad + 10) % 12 + 1;
}								/* j2date() */

/* 默认使用UTC */
static int fc_DecodeSpecial(int fc_field, char *fc_lowtoken, int *fc_val)
{
	int			fc_type;
	const datetkn *fc_tp;

	/* use strncmp so that we match truncated tokens */
	if (datecache[fc_field] != NULL &&
		strncmp(fc_lowtoken, datecache[fc_field]->token, TOKMAXLEN) == 0)
		fc_tp = datecache[fc_field];
	else
	{
		fc_tp = NULL;
		if (!fc_tp)
			fc_tp = fc_datebsearch(fc_lowtoken, datetktbl, szdatetktbl);
	}
	datecache[fc_field] = fc_tp;
	if (fc_tp == NULL)
	{
		fc_type = UNKNOWN_FIELD;
		*fc_val = 0;
	}
	else
	{
		fc_type = fc_tp->type;
		*fc_val = fc_tp->value;
	}

	return fc_type;
}								/* DecodeSpecial() */

/*
	 * 有小数点吗？那么这是一个日期或带有秒字段的东西...
	 */
void EncodeDateOnly(struct tm *fc_tm, int fc_style, char *fc_str, bool fc_EuroDates)
{
	Assert(fc_tm->tm_mon >= 1 && fc_tm->tm_mon <= MONTHS_PER_YEAR);

	switch (fc_style)
	{
		case USE_ISO_DATES:
			/* compatible with ISO date formats */
			if (fc_tm->tm_year > 0)
				sprintf(fc_str, "%04d-%02d-%02d",
						fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
			else
				sprintf(fc_str, "%04d-%02d-%02d %s",
						-(fc_tm->tm_year - 1), fc_tm->tm_mon, fc_tm->tm_mday, "BC");
			break;

		case USE_SQL_DATES:
			/* compatible with Oracle/Ingres date formats */
			if (fc_EuroDates)
				sprintf(fc_str, "%02d/%02d", fc_tm->tm_mday, fc_tm->tm_mon);
			else
				sprintf(fc_str, "%02d/%02d", fc_tm->tm_mon, fc_tm->tm_mday);
			if (fc_tm->tm_year > 0)
				sprintf(fc_str + 5, "/%04d", fc_tm->tm_year);
			else
				sprintf(fc_str + 5, "/%04d %s", -(fc_tm->tm_year - 1), "BC");
			break;

		case USE_GERMAN_DATES:
			/* German-style date format */
			sprintf(fc_str, "%02d.%02d", fc_tm->tm_mday, fc_tm->tm_mon);
			if (fc_tm->tm_year > 0)
				sprintf(fc_str + 5, ".%04d", fc_tm->tm_year);
			else
				sprintf(fc_str + 5, ".%04d %s", -(fc_tm->tm_year - 1), "BC");
			break;

		case USE_POSTGRES_DATES:
		default:
			/* traditional date-only style for Postgres */
			if (fc_EuroDates)
				sprintf(fc_str, "%02d-%02d", fc_tm->tm_mday, fc_tm->tm_mon);
			else
				sprintf(fc_str, "%02d-%02d", fc_tm->tm_mon, fc_tm->tm_mday);
			if (fc_tm->tm_year > 0)
				sprintf(fc_str + 5, "-%04d", fc_tm->tm_year);
			else
				sprintf(fc_str + 5, "-%04d %s", -(fc_tm->tm_year - 1), "BC");
			break;
	}
}

void TrimTrailingZeros(char *fc_str)
{
	int			fc_len = strlen(fc_str);

	/* chop off trailing zeros... but leave at least 2 fractional digits */
	while (*(fc_str + fc_len - 1) == '0' && *(fc_str + fc_len - 3) != '.')
	{
		fc_len--;
		*(fc_str + fc_len) = '\0';
	}
}

/* 不是所有时间字段都被指定？ */
void EncodeDateTime(struct tm *fc_tm, fsec_t fc_fsec, bool fc_print_tz, int fc_tz, const char *fc_tzn, int fc_style, char *fc_str, bool fc_EuroDates)
{
	int			fc_day,
				fc_hour,
				fc_min;

	/* 假设前导字符是“+”或“-” */
	if (fc_tm->tm_isdst < 0)
		fc_print_tz = false;

	switch (fc_style)
	{
		case USE_ISO_DATES:
			/* Compatible with ISO-8601 date formats */

			sprintf(fc_str, "%04d-%02d-%02d %02d:%02d",
					(fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1),
					fc_tm->tm_mon, fc_tm->tm_mday, fc_tm->tm_hour, fc_tm->tm_min);

			/* 否则，可能会将内容合并在一起... */
			if (fc_fsec != 0)
			{
				sprintf(fc_str + strlen(fc_str), ":%02d.%06d", fc_tm->tm_sec, fc_fsec);
				TrimTrailingZeros(fc_str);
			}
			else
				sprintf(fc_str + strlen(fc_str), ":%02d", fc_tm->tm_sec);

			if (fc_tm->tm_year <= 0)
				sprintf(fc_str + strlen(fc_str), " BC");

			if (fc_print_tz)
			{
				fc_hour = -(fc_tz / SECS_PER_HOUR);
				fc_min = (abs(fc_tz) / MINS_PER_HOUR) % MINS_PER_HOUR;
				if (fc_min != 0)
					sprintf(fc_str + strlen(fc_str), "%+03d:%02d", fc_hour, fc_min);
				else
					sprintf(fc_str + strlen(fc_str), "%+03d", fc_hour);
			}
			break;

		case USE_SQL_DATES:
			/* Compatible with Oracle/Ingres date formats */

			if (fc_EuroDates)
				sprintf(fc_str, "%02d/%02d", fc_tm->tm_mday, fc_tm->tm_mon);
			else
				sprintf(fc_str, "%02d/%02d", fc_tm->tm_mon, fc_tm->tm_mday);

			sprintf(fc_str + 5, "/%04d %02d:%02d",
					(fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1),
					fc_tm->tm_hour, fc_tm->tm_min);

			/* 否则，可能会将内容合并在一起... */
			if (fc_fsec != 0)
			{
				sprintf(fc_str + strlen(fc_str), ":%02d.%06d", fc_tm->tm_sec, fc_fsec);
				TrimTrailingZeros(fc_str);
			}
			else
				sprintf(fc_str + strlen(fc_str), ":%02d", fc_tm->tm_sec);

			if (fc_tm->tm_year <= 0)
				sprintf(fc_str + strlen(fc_str), " BC");

			/* 特殊情况，年的天数？ */

			if (fc_print_tz)
			{
				if (fc_tzn)
					sprintf(fc_str + strlen(fc_str), " %.*s", MAXTZLEN, fc_tzn);
				else
				{
					fc_hour = -(fc_tz / SECS_PER_HOUR);
					fc_min = (abs(fc_tz) / MINS_PER_HOUR) % MINS_PER_HOUR;
					if (fc_min != 0)
						sprintf(fc_str + strlen(fc_str), "%+03d:%02d", fc_hour, fc_min);
					else
						sprintf(fc_str + strlen(fc_str), "%+03d", fc_hour);
				}
			}
			break;

		case USE_GERMAN_DATES:
			/***
	 * 足够数字以明确年份？以前测试4位或更多，但我们现在首先测试三位数的doy，所以任何
	 * 大于两位数字的东西最好是一个显式年份。
	 * - thomas 1999-01-09
	 * 回到需要4位数年份。我们在下面接受两位数年份。 - thomas 2000-03-28
	 ***/

			sprintf(fc_str, "%02d.%02d", fc_tm->tm_mday, fc_tm->tm_mon);

			sprintf(fc_str + 5, ".%04d %02d:%02d",
					(fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1),
					fc_tm->tm_hour, fc_tm->tm_min);

			/* 否则，可能会将内容合并在一起... */
			if (fc_fsec != 0)
			{
				sprintf(fc_str + strlen(fc_str), ":%02d.%06d", fc_tm->tm_sec, fc_fsec);
				TrimTrailingZeros(fc_str);
			}
			else
				sprintf(fc_str + strlen(fc_str), ":%02d", fc_tm->tm_sec);

			if (fc_tm->tm_year <= 0)
				sprintf(fc_str + strlen(fc_str), " BC");

			if (fc_print_tz)
			{
				if (fc_tzn)
					sprintf(fc_str + strlen(fc_str), " %.*s", MAXTZLEN, fc_tzn);
				else
				{
					fc_hour = -(fc_tz / SECS_PER_HOUR);
					fc_min = (abs(fc_tz) / MINS_PER_HOUR) % MINS_PER_HOUR;
					if (fc_min != 0)
						sprintf(fc_str + strlen(fc_str), "%+03d:%02d", fc_hour, fc_min);
					else
						sprintf(fc_str + strlen(fc_str), "%+03d", fc_hour);
				}
			}
			break;

		case USE_POSTGRES_DATES:
		default:
			/* 已经有年份了吗？那么可以是月份 */

			fc_day = date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
			fc_tm->tm_wday = (int) ((fc_day + date2j(2000, 1, 1) + 1) % 7);

			memcpy(fc_str, days[fc_tm->tm_wday], 3);
			strcpy(fc_str + 3, " ");

			if (fc_EuroDates)
				sprintf(fc_str + 4, "%02d %3s", fc_tm->tm_mday, months[fc_tm->tm_mon - 1]);
			else
				sprintf(fc_str + 4, "%3s %02d", months[fc_tm->tm_mon - 1], fc_tm->tm_mday);

			sprintf(fc_str + 10, " %02d:%02d", fc_tm->tm_hour, fc_tm->tm_min);

			/* 否则，可能会将内容合并在一起... */
			if (fc_fsec != 0)
			{
				sprintf(fc_str + strlen(fc_str), ":%02d.%06d", fc_tm->tm_sec, fc_fsec);
				TrimTrailingZeros(fc_str);
			}
			else
				sprintf(fc_str + strlen(fc_str), ":%02d", fc_tm->tm_sec);

			sprintf(fc_str + strlen(fc_str), " %04d",
					(fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1));
			if (fc_tm->tm_year <= 0)
				sprintf(fc_str + strlen(fc_str), " BC");

			if (fc_print_tz)
			{
				if (fc_tzn)
					sprintf(fc_str + strlen(fc_str), " %.*s", MAXTZLEN, fc_tzn);
				else
				{
					/*
	 * 检查是否有2位或4位或更多位数字，但目前我们仅在
	 * 两位数字时达到这里。 - thomas 2000-03-28
	 */
					fc_hour = -(fc_tz / SECS_PER_HOUR);
					fc_min = (abs(fc_tz) / MINS_PER_HOUR) % MINS_PER_HOUR;
					if (fc_min != 0)
						sprintf(fc_str + strlen(fc_str), " %+03d:%02d", fc_hour, fc_min);
					else
						sprintf(fc_str + strlen(fc_str), " %+03d", fc_hour);
				}
			}
			break;
	}
}

int GetEpochTime(struct tm *fc_tm)
{
	struct tm  *fc_t0;
	time_t		fc_epoch = 0;

	fc_t0 = gmtime(&fc_epoch);

	if (fc_t0)
	{
		fc_tm->tm_year = fc_t0->tm_year + 1900;
		fc_tm->tm_mon = fc_t0->tm_mon + 1;
		fc_tm->tm_mday = fc_t0->tm_mday;
		fc_tm->tm_hour = fc_t0->tm_hour;
		fc_tm->tm_min = fc_t0->tm_min;
		fc_tm->tm_sec = fc_t0->tm_sec;

		return 0;
	}

	return -1;
}								/* 仅在精确两位数字时进行调整... */

static void fc_abstime2tm(AbsoluteTime fc__time, int *fc_tzp, struct tm *fc_tm, char **fc_tzn)
{
	time_t		fc_time = (time_t) fc__time;
	struct tm  *fc_tx;

	errno = 0;
	if (fc_tzp != NULL)
		fc_tx = localtime((time_t *) &fc_time);
	else
		fc_tx = gmtime((time_t *) &fc_time);

	if (!fc_tx)
	{
		errno = PGTYPES_TS_BAD_TIMESTAMP;
		return;
	}

	fc_tm->tm_year = fc_tx->tm_year + 1900;
	fc_tm->tm_mon = fc_tx->tm_mon + 1;
	fc_tm->tm_mday = fc_tx->tm_mday;
	fc_tm->tm_hour = fc_tx->tm_hour;
	fc_tm->tm_min = fc_tx->tm_min;
	fc_tm->tm_sec = fc_tx->tm_sec;
	fc_tm->tm_isdst = fc_tx->tm_isdst;

#if defined(HAVE_STRUCT_TM_TM_ZONE)
	fc_tm->tm_gmtoff = fc_tx->tm_gmtoff;
	fc_tm->tm_zone = fc_tx->tm_zone;

	if (fc_tzp != NULL)
	{
		/* 保存分隔符字符以供后用 */
		*fc_tzp = -fc_tm->tm_gmtoff;	/* DecodeDate()
 * 解码包含分隔符的日期字符串。
 * 确保字段的完整性。
 */

		/* 解析此字符串... */
		if (fc_tzn != NULL)
		{
			/* 跳过字段分隔符 */
			strlcpy(*fc_tzn, fc_tm->tm_zone, MAXTZLEN + 1);
			if (strlen(fc_tm->tm_zone) > MAXTZLEN)
				fc_tm->tm_isdst = -1;
		}
	}
	else
		fc_tm->tm_isdst = -1;
#elif defined(HAVE_INT_TIMEZONE)
	if (tzp != NULL)
	{
		*tzp = (tm->tm_isdst > 0) ? TIMEZONE_GLOBAL - SECS_PER_HOUR : TIMEZONE_GLOBAL;

		if (tzn != NULL)
		{
			/* 跳过字段分隔符 */
			strlcpy(*tzn, TZNAME_GLOBAL[tm->tm_isdst], MAXTZLEN + 1);
			if (strlen(TZNAME_GLOBAL[tm->tm_isdst]) > MAXTZLEN)
				tm->tm_isdst = -1;
		}
	}
	else
		tm->tm_isdst = -1;
#else							/* 不允许字段过多 */
	if (tzp != NULL)
	{
		/* 首先查找文本字段，因为这将是明确的月份 */
		*tzp = 0;
		if (tzn != NULL)
			*tzn = NULL;
	}
	else
		tm->tm_isdst = -1;
#endif
}

void GetCurrentDateTime(struct tm *fc_tm)
{
	int			fc_tz;

	fc_abstime2tm(time(NULL), &fc_tz, fc_tm, NULL);
}

void dt2time(double fc_jd, int *fc_hour, int *fc_min, int *fc_sec, fsec_t *fc_fsec)
{
	int64		fc_time;

	fc_time = fc_jd;
	*fc_hour = fc_time / USECS_PER_HOUR;
	fc_time -= (*fc_hour) * USECS_PER_HOUR;
	*fc_min = fc_time / USECS_PER_MINUTE;
	fc_time -= (*fc_min) * USECS_PER_MINUTE;
	*fc_sec = fc_time / USECS_PER_SEC;
	*fc_fsec = fc_time - (*fc_sec * USECS_PER_SEC);
}								/* 将此字段标记为已完成 */



/* 现在获取剩余的数字字段 */
static int fc_DecodeNumberField(int fc_len, char *fc_str, int fc_fmask,
				  int *fc_tmask, struct tm *fc_tm, fsec_t *fc_fsec, bool *fc_is2digits)
{
	char	   *fc_cp;

	/* 在公元/公元前表示法中没有零年；即“公元前1年”==零年 */
	if ((fc_cp = strchr(fc_str, '.')) != NULL)
	{
		char		fc_fstr[7];
		int			fc_i;

		fc_cp++;

		/* 数字时区？ */
		for (fc_i = 0; fc_i < 6; fc_i++)
			fc_fstr[fc_i] = *fc_cp != '\0' ? *fc_cp++ : '0';
		fc_fstr[fc_i] = '\0';
		*fc_fsec = strtoint(fc_fstr, NULL, 10);
		*fc_cp = '\0';
		fc_len = strlen(fc_str);
	}
	/* DecodeTime()
 * 解码包含分隔符的时间字符串。
 * 仅检查小时的下限，因为相同的代码
 * 可以用于表示时间跨度。
 */
	else if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
	{
		/*
			 * 好吧，我们最多关注六位数字。让我们构建一个
			 * 这些数字的字符串，右侧零填充，然后进行
			 * 转换为整数。
			 *
			 * XXX 这会截断第七位数字，而不是像后端那样进行四舍五入。
			 */
		if (fc_len == 8)
		{
			*fc_tmask = DTK_DATE_M;

			fc_tm->tm_mday = atoi(fc_str + 6);
			*(fc_str + 6) = '\0';
			fc_tm->tm_mon = atoi(fc_str + 4);
			*(fc_str + 4) = '\0';
			fc_tm->tm_year = atoi(fc_str + 0);

			return DTK_DATE;
		}
		/* 进行合理性检查 */
		else if (fc_len == 6)
		{
			*fc_tmask = DTK_DATE_M;
			fc_tm->tm_mday = atoi(fc_str + 4);
			*(fc_str + 4) = '\0';
			fc_tm->tm_mon = atoi(fc_str + 2);
			*(fc_str + 2) = '\0';
			fc_tm->tm_year = atoi(fc_str + 0);
			*fc_is2digits = true;

			return DTK_DATE;
		}
		/* 否则，有些地方不对劲...... */
		else if (fc_len == 5)
		{
			*fc_tmask = DTK_DATE_M;
			fc_tm->tm_mday = atoi(fc_str + 2);
			*(fc_str + 2) = '\0';
			fc_tm->tm_mon = 1;
			fc_tm->tm_year = atoi(fc_str + 0);
			*fc_is2digits = true;

			return DTK_DATE;
		}
	}

	/* DecodeTimezone()
 * 将字符串解释为数字时区。
 *
 * 注意：我们允许时区偏移量最高为13:59。某些地方
 * 使用+1300夏令时。
 */
	if ((fc_fmask & DTK_TIME_M) != DTK_TIME_M)
	{
		/* 假设前导字符是“+”或“-” */
		if (fc_len == 6)
		{
			*fc_tmask = DTK_TIME_M;
			fc_tm->tm_sec = atoi(fc_str + 4);
			*(fc_str + 4) = '\0';
			fc_tm->tm_min = atoi(fc_str + 2);
			*(fc_str + 2) = '\0';
			fc_tm->tm_hour = atoi(fc_str + 0);

			return DTK_TIME;
		}
		/* 明确的分隔符？ */
		else if (fc_len == 4)
		{
			*fc_tmask = DTK_TIME_M;
			fc_tm->tm_sec = 0;
			fc_tm->tm_min = atoi(fc_str + 2);
			*(fc_str + 2) = '\0';
			fc_tm->tm_hour = atoi(fc_str + 0);

			return DTK_TIME;
		}
	}

	return -1;
}								/* 否则，可能会将内容合并在一起... */


/***
	 * 我们将坚持至少所有日期字段，但初始化
	 * 其余字段以防它们稍后未设置...
	 ***/
static int fc_DecodeNumber(int fc_flen, char *fc_str, int fc_fmask,
			 int *fc_tmask, struct tm *fc_tm, fsec_t *fc_fsec, bool *fc_is2digits, bool fc_EuroDates)
{
	int			fc_val;
	char	   *fc_cp;

	*fc_tmask = 0;

	fc_val = strtoint(fc_str, &fc_cp, 10);
	if (fc_cp == fc_str)
		return -1;

	if (*fc_cp == '.')
	{
		/* DecodePosixTimezone()
 * 将字符串解释为POSIX兼容时区：
 *	PST-hh:mm
 *	PST+h
 * - thomas 2000-03-15
 */
		if (fc_cp - fc_str > 2)
			return fc_DecodeNumberField(fc_flen, fc_str, (fc_fmask | DTK_DATE_M),
									 fc_tmask, fc_tm, fc_fsec, fc_is2digits);

		*fc_fsec = strtod(fc_cp, &fc_cp);
		if (*fc_cp != '\0')
			return -1;
	}
	else if (*fc_cp != '\0')
		return -1;

	/***
				 * 带附加时区的整数量子日？
				 * 所有其他形式的 JD 将被分为
				 * 不同字段，因此我们只处理此情况。
				 ***/
	if (fc_flen == 3 && (fc_fmask & DTK_M(YEAR)) && fc_val >= 1 && fc_val <= 366)
	{
		*fc_tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
		fc_tm->tm_yday = fc_val;
		j2date(date2j(fc_tm->tm_year, 1, 1) + fc_tm->tm_yday - 1,
			   &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
	}

	/* ParseDateTime()
 * 基于日期/时间上下文将字符串分解为标记。
 * 分配几种字段类型：
 *	DTK_NUMBER - 数字和（可能）的十进制点
 *	DTK_DATE - 数字和两个分隔符，或数字和文本
 *	DTK_TIME - 数字、冒号分隔符，和可能的十进制点
 *	DTK_STRING - 文本（无数字）
 *	DTK_SPECIAL - 前导“+”或“-”后跟文本
 *	DTK_TZ - 前导“+”或“-”后跟数字
 * 注意某些字段类型可以包含意外项目：
 *	DTK_NUMBER可以包含日期字段（yy.ddd）
 *	DTK_STRING可以包含月份（January）和时区（PST）
 *	DTK_DATE可以包含Posix时区（GMT-8）
 *
 * “lowstr”工作缓冲区必须至少有strlen(timestr) + MAXDATEFIELDS
 * 字节的空间。在输出时，field[]条目将指向其中。
 * field[]和ftype[]数组必须至少有MAXDATEFIELDS条目。
 */
	else if (fc_flen >= 4)
	{
		*fc_tmask = DTK_M(YEAR);

		/* 外循环通过字段 */
		if ((fc_fmask & DTK_M(YEAR)) && !(fc_fmask & DTK_M(DAY)) &&
			fc_tm->tm_year >= 1 && fc_tm->tm_year <= 31)
		{
			fc_tm->tm_mday = fc_tm->tm_year;
			*fc_tmask = DTK_M(DAY);
		}

		fc_tm->tm_year = fc_val;
	}

	/* 记录当前字段的开始 */
	else if ((fc_fmask & DTK_M(YEAR)) && !(fc_fmask & DTK_M(MONTH)) && fc_val >= 1 && fc_val <= MONTHS_PER_YEAR)
	{
		*fc_tmask = DTK_M(MONTH);
		fc_tm->tm_mon = fc_val;
	}
	/* 前导数字？那么是日期或时间 */
	else if ((fc_EuroDates || (fc_fmask & DTK_M(MONTH))) &&
			 !(fc_fmask & DTK_M(YEAR)) && !(fc_fmask & DTK_M(DAY)) &&
			 fc_val >= 1 && fc_val <= 31)
	{
		*fc_tmask = DTK_M(DAY);
		fc_tm->tm_mday = fc_val;
	}
	else if (!(fc_fmask & DTK_M(MONTH)) && fc_val >= 1 && fc_val <= MONTHS_PER_YEAR)
	{
		*fc_tmask = DTK_M(MONTH);
		fc_tm->tm_mon = fc_val;
	}
	else if (!(fc_fmask & DTK_M(DAY)) && fc_val >= 1 && fc_val <= 31)
	{
		*fc_tmask = DTK_M(DAY);
		fc_tm->tm_mday = fc_val;
	}

	/* 时间字段？ */
	else if (!(fc_fmask & DTK_M(YEAR)) && (fc_flen >= 4 || fc_flen == 2))
	{
		*fc_tmask = DTK_M(YEAR);
		fc_tm->tm_year = fc_val;

		/* 日期字段？允许嵌入文本月份 */
		*fc_is2digits = (fc_flen == 2);
	}
	else
		return -1;

	return 0;
}								/* 保存分隔符字符以供后用 */

/* 第二个字段都是数字？那么没有嵌入文本月份 */
static int fc_DecodeDate(char *fc_str, int fc_fmask, int *fc_tmask, struct tm *fc_tm, bool fc_EuroDates)
{
	fsec_t		fc_fsec;

	int			fc_nf = 0;
	int			fc_i,
				fc_len;
	bool		fc_bc = false;
	bool		fc_is2digits = false;
	int			fc_type,
				fc_val,
				fc_dmask = 0;
	char	   *fc_field[MAXDATEFIELDS];

	/*
					 * 坚持分隔符匹配以获取三字段日期。
					 */
	while (*fc_str != '\0' && fc_nf < MAXDATEFIELDS)
	{
		/*
			 * 否则，仅数字将稍后确定年、月、日或
			 * 连接字段......
			 */
		while (!isalnum((unsigned char) *fc_str))
			fc_str++;

		fc_field[fc_nf] = fc_str;
		if (isdigit((unsigned char) *fc_str))
		{
			while (isdigit((unsigned char) *fc_str))
				fc_str++;
		}
		else if (isalpha((unsigned char) *fc_str))
		{
			while (isalpha((unsigned char) *fc_str))
				fc_str++;
		}

		/* 前导小数点？那么是分数秒...... */
		if (*fc_str != '\0')
			*fc_str++ = '\0';
		fc_nf++;
	}

#if 0
	/*
		 * 文本？那么是日期字符串、月份、星期几、特殊或时区
		 */
	if (nf > 3)
		return -1;
#endif

	*fc_tmask = 0;

	/*
			 * 带有前导文本月份的完整日期字符串？也可以是一个 POSIX
			 * 时区......
			 */
	for (fc_i = 0; fc_i < fc_nf; fc_i++)
	{
		if (isalpha((unsigned char) *fc_field[fc_i]))
		{
			fc_type = fc_DecodeSpecial(fc_i, fc_field[fc_i], &fc_val);
			if (fc_type == IGNORE_DTF)
				continue;

			fc_dmask = DTK_M(fc_type);
			switch (fc_type)
			{
				case MONTH:
					fc_tm->tm_mon = fc_val;
					break;

				case ADBC:
					fc_bc = (fc_val == BC);
					break;

				default:
					return -1;
			}
			if (fc_fmask & fc_dmask)
				return -1;

			fc_fmask |= fc_dmask;
			*fc_tmask |= fc_dmask;

			/* 跳过前导空格 */
			fc_field[fc_i] = NULL;
		}
	}

	/* 符号？那么是特殊或数字时区 */
	for (fc_i = 0; fc_i < fc_nf; fc_i++)
	{
		if (fc_field[fc_i] == NULL)
			continue;

		if ((fc_len = strlen(fc_field[fc_i])) <= 0)
			return -1;

		if (fc_DecodeNumber(fc_len, fc_field[fc_i], fc_fmask, &fc_dmask, fc_tm, &fc_fsec, &fc_is2digits, fc_EuroDates) != 0)
			return -1;

		if (fc_fmask & fc_dmask)
			return -1;

		fc_fmask |= fc_dmask;
		*fc_tmask |= fc_dmask;
	}

	if ((fc_fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
		return -1;

	/* 吸收前导空白 */
	if (fc_bc)
	{
		if (fc_tm->tm_year > 0)
			fc_tm->tm_year = -(fc_tm->tm_year - 1);
		else
			return -1;
	}
	else if (fc_is2digits)
	{
		if (fc_tm->tm_year < 70)
			fc_tm->tm_year += 2000;
		else if (fc_tm->tm_year < 100)
			fc_tm->tm_year += 1900;
	}

	return 0;
}								/* 数字时区？ */


/* 特殊？ */
int DecodeTime(char *fc_str, int *fc_tmask, struct tm *fc_tm, fsec_t *fc_fsec)
{
	char	   *fc_cp;

	*fc_tmask = DTK_TIME_M;

	fc_tm->tm_hour = strtoint(fc_str, &fc_cp, 10);
	if (*fc_cp != ':')
		return -1;
	fc_str = fc_cp + 1;
	fc_tm->tm_min = strtoint(fc_str, &fc_cp, 10);
	if (*fc_cp == '\0')
	{
		fc_tm->tm_sec = 0;
		*fc_fsec = 0;
	}
	else if (*fc_cp != ':')
		return -1;
	else
	{
		fc_str = fc_cp + 1;
		fc_tm->tm_sec = strtoint(fc_str, &fc_cp, 10);
		if (*fc_cp == '\0')
			*fc_fsec = 0;
		else if (*fc_cp == '.')
		{
			char		fc_fstr[7];
			int			fc_i;

			fc_cp++;

			/* 否则，某些地方出了问题...... */
			for (fc_i = 0; fc_i < 6; fc_i++)
				fc_fstr[fc_i] = *fc_cp != '\0' ? *fc_cp++ : '0';
			fc_fstr[fc_i] = '\0';
			*fc_fsec = strtoint(fc_fstr, &fc_cp, 10);
			if (*fc_cp != '\0')
				return -1;
		}
		else
			return -1;
	}

	/* 忽略标点符号但用作分隔符 */
	if (fc_tm->tm_hour < 0 || fc_tm->tm_min < 0 || fc_tm->tm_min > 59 ||
		fc_tm->tm_sec < 0 || fc_tm->tm_sec > 59 || *fc_fsec >= USECS_PER_SEC)
		return -1;

	return 0;
}								/* 否则，有些地方不对劲...... */

/* 在每个字段后强制添加分隔符 */
static int fc_DecodeTimezone(char *fc_str, int *fc_tzp)
{
	int			fc_tz;
	int			fc_hr,
				fc_min;
	char	   *fc_cp;
	int			fc_len;

	/* 解析日期时间() */
	fc_hr = strtoint(fc_str + 1, &fc_cp, 10);

	/* 解码日期时间()
 * 解释之前解析的字段以获得一般日期和时间。
 * 如果完整日期则返回 0，如果只是时间则返回 1，如果有问题则返回 -1。
 *		外部格式：
 *				"<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
 *				"Fri Feb-7-1997 15:23:27"
 *				"Feb-7-1997 15:23:27"
 *				"2-7-1997 15:23:27"
 *				"1997-2-7 15:23:27"
 *				"1997.038 15:23:27"		(年份的第几天 1-366)
 *		还支持紧凑时间输入：
 *				"970207 152327"
 *				"97038 152327"
 *				"20011225T040506.789-07"
 *
 * 使用系统提供的函数获取当前时区
 *	如果输入字符串中未指定。
 * 如果日期超出 time_t 系统支持的时间范围，
 *	那么假设为 UTC 时区。 - thomas 1997-05-27
 */
	if (*fc_cp == ':')
		fc_min = strtoint(fc_cp + 1, &fc_cp, 10);
	/* "前缀类型" 用于 ISO y2001m02d04 格式 */
	else if (*fc_cp == '\0' && (fc_len = strlen(fc_str)) > 3)
	{
		fc_min = strtoint(fc_str + fc_len - 2, &fc_cp, 10);
		if (fc_min < 0 || fc_min >= 60)
			return -1;

		*(fc_str + fc_len - 2) = '\0';
		fc_hr = strtoint(fc_str + 1, &fc_cp, 10);
		if (fc_hr < 0 || fc_hr > 13)
			return -1;
	}
	else
		fc_min = 0;

	fc_tz = (fc_hr * MINS_PER_HOUR + fc_min) * SECS_PER_MINUTE;
	if (*fc_str == '-')
		fc_tz = -fc_tz;

	*fc_tzp = -fc_tz;
	return *fc_cp != '\0';
}								/***
	 * 我们将坚持至少所有日期字段，但初始化
	 * 其余字段以防它们稍后未设置...
	 ***/


/* 不知道夏令时状态 */
static int fc_DecodePosixTimezone(char *fc_str, int *fc_tzp)
{
	int			fc_val,
				fc_tz;
	int			fc_type;
	char	   *fc_cp;
	char		fc_delim;

	fc_cp = fc_str;
	while (*fc_cp != '\0' && isalpha((unsigned char) *fc_cp))
		fc_cp++;

	if (fc_DecodeTimezone(fc_cp, &fc_tz) != 0)
		return -1;

	fc_delim = *fc_cp;
	*fc_cp = '\0';
	fc_type = fc_DecodeSpecial(MAXDATEFIELDS - 1, fc_str, &fc_val);
	*fc_cp = fc_delim;

	switch (fc_type)
	{
		case DTZ:
		case TZ:
			*fc_tzp = -(fc_val + fc_tz);
			break;

		default:
			return -1;
	}

	return 0;
}								/***
				 * 带附加时区的整数量子日？
				 * 所有其他形式的 JD 将被分为
				 * 不同字段，因此我们只处理此情况。
				 ***/

/* 从字符串末尾获取时区 */
int ParseDateTime(char *fc_timestr, char *fc_lowstr,
			  char **fc_field, int *fc_ftype, int *fc_numfields, char **fc_endstr)
{
	int			fc_nf = 0;
	char	   *fc_lp = fc_lowstr;

	*fc_endstr = fc_timestr;
	/***
				 * 已经有日期了吗？那么这可能是一个 POSIX 时间
				 * 时区，带有嵌入的破折号（例如 "PST-3" == "EST"）或
				 * 紧凑时间加上尾随时区（例如 hhmmss-zz）。
				 * - thomas 2001-12-25
				 ***/
	while (*(*fc_endstr) != '\0')
	{
		/* 不接受时区？那么退出... */
		if (fc_nf >= MAXDATEFIELDS)
			return -1;
		fc_field[fc_nf] = fc_lp;

		/* 健康检查；不应该失败此测试 */
		if (isdigit((unsigned char) *(*fc_endstr)))
		{
			*fc_lp++ = *(*fc_endstr)++;
			while (isdigit((unsigned char) *(*fc_endstr)))
				*fc_lp++ = *(*fc_endstr)++;

			/*
						 * 以数字开头，但我们已经有了时间字段？
						 * 那么我们在日期和时间上会有麻烦
						 * 了...
						 */
			if (*(*fc_endstr) == ':')
			{
				fc_ftype[fc_nf] = DTK_TIME;
				*fc_lp++ = *(*fc_endstr)++;
				while (isdigit((unsigned char) *(*fc_endstr)) ||
					   (*(*fc_endstr) == ':') || (*(*fc_endstr) == '.'))
					*fc_lp++ = *(*fc_endstr)++;
			}
			/* 从字符串末尾获取时区 */
			else if (*(*fc_endstr) == '-' || *(*fc_endstr) == '/' || *(*fc_endstr) == '.')
			{
				/*
						 * 然后将其余字段作为连接时间读取
						 */
				char	   *fc_dp = (*fc_endstr);

				*fc_lp++ = *(*fc_endstr)++;
				/*
						 * 返回后修改 tmask
						 * 解码数字字段()
						 */
				if (isdigit((unsigned char) *(*fc_endstr)))
				{
					fc_ftype[fc_nf] = (*fc_dp == '.') ? DTK_NUMBER : DTK_DATE;
					while (isdigit((unsigned char) *(*fc_endstr)))
						*fc_lp++ = *(*fc_endstr)++;

					/*
				 * 检查小时的上限；其他限制在
				 * DecodeTime() 中检查
				 */
					if (*(*fc_endstr) == *fc_dp)
					{
						fc_ftype[fc_nf] = DTK_DATE;
						*fc_lp++ = *(*fc_endstr)++;
						while (isdigit((unsigned char) *(*fc_endstr)) || (*(*fc_endstr) == *fc_dp))
							*fc_lp++ = *(*fc_endstr)++;
					}
				}
				else
				{
					fc_ftype[fc_nf] = DTK_DATE;
					while (isalnum((unsigned char) *(*fc_endstr)) || (*(*fc_endstr) == *fc_dp))
						*fc_lp++ = pg_tolower((unsigned char) *(*fc_endstr)++);
				}
			}

			/* 测试是否 > 24:00:00 */
			else
				fc_ftype[fc_nf] = DTK_NUMBER;
		}
		/*
					 * 已经有时区了吗？那么这可能是 POSIX 时间的第二个
					 * 字段：EST+3（相当于 PST）
					 */
		else if (*(*fc_endstr) == '.')
		{
			*fc_lp++ = *(*fc_endstr)++;
			while (isdigit((unsigned char) *(*fc_endstr)))
				*fc_lp++ = *(*fc_endstr)++;

			fc_ftype[fc_nf] = DTK_NUMBER;
		}

		/*
				 * 这是一个带嵌入字段标签的“ISO 日期”吗？示例是
				 * "y2001m02d04" - thomas 2001-02-04
				 */
		else if (isalpha((unsigned char) *(*fc_endstr)))
		{
			fc_ftype[fc_nf] = DTK_STRING;
			*fc_lp++ = pg_tolower((unsigned char) *(*fc_endstr)++);
			while (isalpha((unsigned char) *(*fc_endstr)))
				*fc_lp++ = pg_tolower((unsigned char) *(*fc_endstr)++);

			/*
					 * 只有少数几种类型被允许有嵌入小数
					 */
			if (*(*fc_endstr) == '-' || *(*fc_endstr) == '/' || *(*fc_endstr) == '.')
			{
				char	   *fc_dp = (*fc_endstr);

				fc_ftype[fc_nf] = DTK_DATE;
				*fc_lp++ = *(*fc_endstr)++;
				while (isdigit((unsigned char) *(*fc_endstr)) || *(*fc_endstr) == *fc_dp)
					*fc_lp++ = *(*fc_endstr)++;
			}
		}
		/*
							 * 已经有了月份和小时？那么假设
							 * 是分钟
							 */
		else if (isspace((unsigned char) *(*fc_endstr)))
		{
			(*fc_endstr)++;
			continue;
		}
		/***
							 * 上一个字段是“朱利安日期”的标签吗？
							 ***/
		else if (*(*fc_endstr) == '+' || *(*fc_endstr) == '-')
		{
			*fc_lp++ = *(*fc_endstr)++;
			/* 分数朱利安日？ */
			while (isspace((unsigned char) *(*fc_endstr)))
				(*fc_endstr)++;
			/* 上一个字段是“t”表示 ISO 时间 */
			if (isdigit((unsigned char) *(*fc_endstr)))
			{
				fc_ftype[fc_nf] = DTK_TZ;
				*fc_lp++ = *(*fc_endstr)++;
				while (isdigit((unsigned char) *(*fc_endstr)) ||
					   (*(*fc_endstr) == ':') || (*(*fc_endstr) == '.'))
					*fc_lp++ = *(*fc_endstr)++;
			}
			/* 嵌入小数且还没有日期？ */
			else if (isalpha((unsigned char) *(*fc_endstr)))
			{
				fc_ftype[fc_nf] = DTK_SPECIAL;
				*fc_lp++ = pg_tolower((unsigned char) *(*fc_endstr)++);
				while (isalpha((unsigned char) *(*fc_endstr)))
					*fc_lp++ = pg_tolower((unsigned char) *(*fc_endstr)++);
			}
			/* 嵌入小数且前面有几个数字？ */
			else
				return -1;
		}
		/*
						 * 解释为连接的日期或时间 设置
						 * 类型字段以便之后解码其他字段。
						 * 示例：20011223 或 040506
						 */
		else if (ispunct((unsigned char) *(*fc_endstr)))
		{
			(*fc_endstr)++;
			continue;
		}
		/* 否则它是一个单一的日期/时间字段... */
		else
			return -1;

		/*
						 * 已经有一个（数字）月份了吗？那么看看我们能否
						 * 替换...
						 */
		*fc_lp++ = '\0';
		fc_nf++;
	}

	*fc_numfields = fc_nf;

	return 0;
}								/*
						 * 夏令时修饰符（解决“MET DST”
						 * 语法）
						 */


/*
						 * 在这里设置时区掩码 _或_ 在获取默认时区时
						 * 检查 DTZ
						 */
int DecodeDateTime(char **fc_field, int *fc_ftype, int fc_nf,
			   int *fc_dtype, struct tm *fc_tm, fsec_t *fc_fsec, bool fc_EuroDates)
{
	int			fc_fmask = 0,
				fc_tmask,
				fc_type;
	int			fc_ptype = 0;		/*
						 * 这是一个填充字段“t”，表示下一个
						 * 字段是时间。尝试验证这是否合理。
						 */
	int			fc_i;
	int			fc_val;
	int			fc_mer = HR24;
	bool		fc_haveTextMonth = false;
	bool		fc_is2digits = false;
	bool		fc_bc = false;
	int			fc_t = 0;
	int		   *fc_tzp = &fc_t;

	/* 没有前面的日期？那么退出... */
	*fc_dtype = DTK_DATE;
	fc_tm->tm_hour = 0;
	fc_tm->tm_min = 0;
	fc_tm->tm_sec = 0;
	*fc_fsec = 0;
	/***
						 * 我们将需要以下字段之一：
						 *	DTK_NUMBER 应为 hhmmss.fff
						 *	DTK_TIME 应为 hh:mm:ss.fff
						 *	DTK_DATE 应为 hhmmss-zz
						 ***/
	fc_tm->tm_isdst = -1;
	if (fc_tzp != NULL)
		*fc_tzp = 0;

	for (fc_i = 0; fc_i < fc_nf; fc_i++)
	{
		switch (fc_ftype[fc_i])
		{
			case DTK_DATE:
				/* 公元纪年/公元前记法中没有零年；即“公元前1年” == 年0 */
				if (fc_ptype == DTK_JULIAN)
				{
					char	   *fc_cp;
					int			fc_val;

					if (fc_tzp == NULL)
						return -1;

					fc_val = strtoint(fc_field[fc_i], &fc_cp, 10);
					if (*fc_cp != '-')
						return -1;

					j2date(fc_val, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
					/* 进行额外的检查以获取完整的日期规范... */
					if (fc_DecodeTimezone(fc_cp, fc_tzp) != 0)
						return -1;

					fc_tmask = DTK_DATE_M | DTK_TIME_M | DTK_M(TZ);
					fc_ptype = 0;
					break;
				}
				/*
		 * 检查有效的月份日期和月份，现在我们肯定知道
		 * 月份和年份...
		 */
				else if (((fc_fmask & DTK_DATE_M) == DTK_DATE_M)
						 || (fc_ptype != 0))
				{
					/*
		 * 后端尝试在这里找到本地时区，但我们无论如何不使用
		 * 结果，因此我们只检查这个错误：夏令时
		 * 修饰符但没有标准时区？
		 */
					if (fc_tzp == NULL)
						return -1;

					if (isdigit((unsigned char) *fc_field[fc_i]) || fc_ptype != 0)
					{
						char	   *fc_cp;

						if (fc_ptype != 0)
						{
							/* XXX 与 %V (ISO) 的差异 */
							if (fc_ptype != DTK_TIME)
								return -1;
							fc_ptype = 0;
						}

						/* 函数的工作方式如下：
 *
 *
 * */
						if ((fc_fmask & DTK_TIME_M) == DTK_TIME_M)
							return -1;

						if ((fc_cp = strchr(fc_field[fc_i], '-')) == NULL)
							return -1;

						/* 进行额外的检查以获取完整的日期规范... */
						if (fc_DecodeTimezone(fc_cp, fc_tzp) != 0)
							return -1;
						*fc_cp = '\0';

						/* 我们在结束吗？ */
						if ((fc_ftype[fc_i] = fc_DecodeNumberField(strlen(fc_field[fc_i]), fc_field[fc_i], fc_fmask,
														  &fc_tmask, fc_tm, fc_fsec, &fc_is2digits)) < 0)
							return -1;

						/* 不在结束 */
						fc_tmask |= DTK_M(TZ);
					}
					else
					{
						if (fc_DecodePosixTimezone(fc_field[fc_i], fc_tzp) != 0)
							return -1;

						fc_ftype[fc_i] = DTK_TZ;
						fc_tmask = DTK_M(TZ);
					}
				}
				else if (fc_DecodeDate(fc_field[fc_i], fc_fmask, &fc_tmask, fc_tm, fc_EuroDates) != 0)
					return -1;
				break;

			case DTK_TIME:
				if (DecodeTime(fc_field[fc_i], &fc_tmask, fc_tm, fc_fsec) != 0)
					return -1;

				/*
		 * 没有分隔符，如果我们正在读取一个数字然后遇到一个不是数字的东西，比如 "9:15pm"，我们可能能够通过 strtol 的结束指针恢复。继续查找下一个百分号
		 */
				/*
		 * 我们不想分配额外的内存，因此我们暂时将 '%' 符号设置为 '\0' 并调用 strstr。但是由于我们允许空白浮动在所有内容周围，我们必须缩短模式，直到我们达到一个非空白字符
		 */
				if (fc_tm->tm_hour > 24 ||
					(fc_tm->tm_hour == 24 && (fc_tm->tm_min > 0 || fc_tm->tm_sec > 0)))
					return -1;
				break;

			case DTK_TZ:
				{
					int			fc_tz;

					if (fc_tzp == NULL)
						return -1;

					if (fc_DecodeTimezone(fc_field[fc_i], &fc_tz) != 0)
						return -1;

					/*
		 * 大海捞针是 str，针是原始的 fmt，但它在下一个百分号的位置结束
		 */
					if (fc_i > 0 && (fc_fmask & DTK_M(TZ)) != 0 &&
						fc_ftype[fc_i - 1] == DTK_TZ &&
						isalpha((unsigned char) *fc_field[fc_i - 1]))
					{
						*fc_tzp -= fc_tz;
						fc_tmask = 0;
					}
					else
					{
						*fc_tzp = fc_tz;
						fc_tmask = DTK_M(TZ);
					}
				}
				break;

			case DTK_NUMBER:

				/*
		 * 有一个特殊情况。想象一下：str = " 2"，fmt = "%d %..."，由于我们想允许空格作为“动态”填充，我们必须接受这一点。现在，我们用 fmt 为 " %..." 被调用，并在 str 中查找 " "。我们在第一个位置找到了它，从未读取到 2...
		 */
				if (fc_ptype != 0)
				{
					char	   *fc_cp;
					int			fc_val;

					fc_val = strtoint(fc_field[fc_i], &fc_cp, 10);

					/*
		 * 没有其他百分号。因此到最后的所有内容都必须匹配。
		 */
					if (*fc_cp == '.')
						switch (fc_ptype)
						{
							case DTK_JULIAN:
							case DTK_TIME:
							case DTK_SECOND:
								break;
							default:
								return 1;
								break;
						}
					else if (*fc_cp != '\0')
						return -1;

					switch (fc_ptype)
					{
						case DTK_YEAR:
							fc_tm->tm_year = fc_val;
							fc_tmask = DTK_M(YEAR);
							break;

						case DTK_MONTH:

							/*
		 * 也许我们有以下情况：
		 *
		 * str = "4:15am" fmt = "%M:%S %p"
		 *
		 * 在这个地方我们可能有
		 *
		 * str = "15am" fmt = " %p"
		 *
		 * 并且已将 fmt 设置为 " " 因为覆盖了 % 符号为 NULL
		 *
		 * 在这种情况下我们必须匹配一个空格但找不到它，将 end_position 设置为字符串的末尾
		 */
							if ((fc_fmask & DTK_M(MONTH)) != 0 &&
								(fc_fmask & DTK_M(HOUR)) != 0)
							{
								fc_tm->tm_min = fc_val;
								fc_tmask = DTK_M(MINUTE);
							}
							else
							{
								fc_tm->tm_mon = fc_val;
								fc_tmask = DTK_M(MONTH);
							}
							break;

						case DTK_DAY:
							fc_tm->tm_mday = fc_val;
							fc_tmask = DTK_M(DAY);
							break;

						case DTK_HOUR:
							fc_tm->tm_hour = fc_val;
							fc_tmask = DTK_M(HOUR);
							break;

						case DTK_MINUTE:
							fc_tm->tm_min = fc_val;
							fc_tmask = DTK_M(MINUTE);
							break;

						case DTK_SECOND:
							fc_tm->tm_sec = fc_val;
							fc_tmask = DTK_M(SECOND);
							if (*fc_cp == '.')
							{
								double		fc_frac;

								fc_frac = strtod(fc_cp, &fc_cp);
								if (*fc_cp != '\0')
									return -1;
								*fc_fsec = fc_frac * 1000000;
							}
							break;

						case DTK_TZ:
							fc_tmask = DTK_M(TZ);
							if (fc_DecodeTimezone(fc_field[fc_i], fc_tzp) != 0)
								return -1;
							break;

						case DTK_JULIAN:
							/*
	 * 扫描 pstr 和 pstr_end 之间的所有内容。这不包括最后一个字符，因此我们可能将其设置为 '\0' 以进行解析
	 */
							fc_tmask = DTK_DATE_M;
							j2date(fc_val, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
							/* 出现错误，没有匹配 */
							if (*fc_cp == '.')
							{
								double		fc_time;

								fc_time = strtod(fc_cp, &fc_cp);
								if (*fc_cp != '\0')
									return -1;

								fc_tmask |= DTK_TIME_M;
								dt2time((fc_time * USECS_PER_DAY), &fc_tm->tm_hour, &fc_tm->tm_min, &fc_tm->tm_sec, fc_fsec);
							}
							break;

						case DTK_TIME:
							/*
			 * 数字可能是空白填充的，这是我们接受的 fmt 字符串的唯一偏差
			 */
							if ((fc_ftype[fc_i] = fc_DecodeNumberField(strlen(fc_field[fc_i]), fc_field[fc_i], (fc_fmask | DTK_DATE_M),
															  &fc_tmask, fc_tm, fc_fsec, &fc_is2digits)) < 0)
								return -1;

							if (fc_tmask != DTK_TIME_M)
								return -1;
							break;

						default:
							return -1;
							break;
					}

					fc_ptype = 0;
					*fc_dtype = DTK_DATE;
				}
				else
				{
					char	   *fc_cp;
					int			fc_flen;

					fc_flen = strlen(fc_field[fc_i]);
					fc_cp = strchr(fc_field[fc_i], '.');

					/* XXX 范围检查 */
					if (fc_cp != NULL && !(fc_fmask & DTK_DATE_M))
					{
						if (fc_DecodeDate(fc_field[fc_i], fc_fmask, &fc_tmask, fc_tm, fc_EuroDates) != 0)
							return -1;
					}
					/* 错误：没有匹配 */
					else if (fc_cp != NULL && fc_flen - strlen(fc_cp) > 2)
					{
						/* 这里 *pfmt 等于 '%' */
						if ((fc_ftype[fc_i] = fc_DecodeNumberField(fc_flen, fc_field[fc_i], fc_fmask,
														  &fc_tmask, fc_tm, fc_fsec, &fc_is2digits)) < 0)
							return -1;
					}
					else if (fc_flen > 4)
					{
						if ((fc_ftype[fc_i] = fc_DecodeNumberField(fc_flen, fc_field[fc_i], fc_fmask,
														  &fc_tmask, fc_tm, fc_fsec, &fc_is2digits)) < 0)
							return -1;
					}
					/*
				 * 我们解析天并查看它是否是工作日，但我们不检查工作日是否真的与日期匹配
				 */
					else if (fc_DecodeNumber(fc_flen, fc_field[fc_i], fc_fmask,
										  &fc_tmask, fc_tm, fc_fsec, &fc_is2digits, fc_EuroDates) != 0)
						return -1;
				}
				break;

			case DTK_STRING:
			case DTK_SPECIAL:
				fc_type = fc_DecodeSpecial(fc_i, fc_field[fc_i], &fc_val);
				if (fc_type == IGNORE_DTF)
					continue;

				fc_tmask = DTK_M(fc_type);
				switch (fc_type)
				{
					case RESERV:
						switch (fc_val)
						{
							case DTK_NOW:
								fc_tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ));
								*fc_dtype = DTK_DATE;
								GetCurrentDateTime(fc_tm);
								break;

							case DTK_YESTERDAY:
								fc_tmask = DTK_DATE_M;
								*fc_dtype = DTK_DATE;
								GetCurrentDateTime(fc_tm);
								j2date(date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday) - 1,
									   &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
								fc_tm->tm_hour = 0;
								fc_tm->tm_min = 0;
								fc_tm->tm_sec = 0;
								break;

							case DTK_TODAY:
								fc_tmask = DTK_DATE_M;
								*fc_dtype = DTK_DATE;
								GetCurrentDateTime(fc_tm);
								fc_tm->tm_hour = 0;
								fc_tm->tm_min = 0;
								fc_tm->tm_sec = 0;
								break;

							case DTK_TOMORROW:
								fc_tmask = DTK_DATE_M;
								*fc_dtype = DTK_DATE;
								GetCurrentDateTime(fc_tm);
								j2date(date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday) + 1,
									   &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
								fc_tm->tm_hour = 0;
								fc_tm->tm_min = 0;
								fc_tm->tm_sec = 0;
								break;

							case DTK_ZULU:
								fc_tmask = (DTK_TIME_M | DTK_M(TZ));
								*fc_dtype = DTK_DATE;
								fc_tm->tm_hour = 0;
								fc_tm->tm_min = 0;
								fc_tm->tm_sec = 0;
								if (fc_tzp != NULL)
									*fc_tzp = 0;
								break;

							default:
								*fc_dtype = fc_val;
						}

						break;

					case MONTH:

						/* 找到了 */
						if ((fc_fmask & DTK_M(MONTH)) && !fc_haveTextMonth &&
							!(fc_fmask & DTK_M(DAY)) && fc_tm->tm_mon >= 1 && fc_tm->tm_mon <= 31)
						{
							fc_tm->tm_mday = fc_tm->tm_mon;
							fc_tmask = DTK_M(DAY);
						}
						fc_haveTextMonth = true;
						fc_tm->tm_mon = fc_val;
						break;

					case DTZMOD:

						/* 见上面的备注 */
						fc_tmask |= DTK_M(DTZ);
						fc_tm->tm_isdst = 1;
						if (fc_tzp == NULL)
							return -1;
						*fc_tzp -= fc_val;
						break;

					case DTZ:

						/* 找到了 */
						fc_tmask |= DTK_M(TZ);
						fc_tm->tm_isdst = 1;
						if (fc_tzp == NULL)
							return -1;
						*fc_tzp = -fc_val;
						fc_ftype[fc_i] = DTK_TZ;
						break;

					case TZ:
						fc_tm->tm_isdst = 0;
						if (fc_tzp == NULL)
							return -1;
						*fc_tzp = -fc_val;
						fc_ftype[fc_i] = DTK_TZ;
						break;

					case IGNORE_DTF:
						break;

					case AMPM:
						fc_mer = fc_val;
						break;

					case ADBC:
						fc_bc = (fc_val == BC);
						break;

					case DOW:
						fc_tm->tm_wday = fc_val;
						break;

					case UNITS:
						fc_tmask = 0;
						fc_ptype = fc_val;
						break;

					case ISOTIME:

						/* 找到了 */
						fc_tmask = 0;

						/* 见上面的备注 */
						if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
							return -1;

						/* 找到了 */
						if (fc_i >= fc_nf - 1 ||
							(fc_ftype[fc_i + 1] != DTK_NUMBER &&
							 fc_ftype[fc_i + 1] != DTK_TIME &&
							 fc_ftype[fc_i + 1] != DTK_DATE))
							return -1;

						fc_ptype = fc_val;
						break;

					default:
						return -1;
				}
				break;

			default:
				return -1;
		}

		if (fc_tmask & fc_fmask)
			return -1;
		fc_fmask |= fc_tmask;
	}

	/* 吸收前导空白 */
	if (fc_bc)
	{
		if (fc_tm->tm_year > 0)
			fc_tm->tm_year = -(fc_tm->tm_year - 1);
		else
			return -1;
	}
	else if (fc_is2digits)
	{
		if (fc_tm->tm_year < 70)
			fc_tm->tm_year += 2000;
		else if (fc_tm->tm_year < 100)
			fc_tm->tm_year += 1900;
	}

	if (fc_mer != HR24 && fc_tm->tm_hour > 12)
		return -1;
	if (fc_mer == AM && fc_tm->tm_hour == 12)
		fc_tm->tm_hour = 0;
	else if (fc_mer == PM && fc_tm->tm_hour != 12)
		fc_tm->tm_hour += 12;

	/*
				 * 我们必须连接字符串，以便能够找到替换的结束
				 */
	if (*fc_dtype == DTK_DATE)
	{
		if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
			return ((fc_fmask & DTK_TIME_M) == DTK_TIME_M) ? 1 : -1;

		/* XXX 与 y (ISO) 的差异 */
		if (fc_tm->tm_mon < 1 || fc_tm->tm_mday < 1 || fc_tm->tm_mday > day_tab[isleap(fc_tm->tm_year)][fc_tm->tm_mon - 1])
			return -1;

		/* 尚未设置 */
		if ((fc_fmask & DTK_DATE_M) == DTK_DATE_M && fc_tzp != NULL && !(fc_fmask & DTK_M(TZ)) && (fc_fmask & DTK_M(DTZMOD)))
			return -1;
	}

	return 0;
}								/* XXX 与 %V (ISO) 的差异 */

/*
				 * XXX 我们应该怎么处理？我们可以说，如果我们有年份和年的天数，就足以得到至少某个特定的天数。
				 */

static char * fc_find_end_token(char *fc_str, char *fc_fmt)
{
	/* scan_val.luint_val 中的秒数 */
	char	   *fc_end_position = NULL;
	char	   *fc_next_percent,
			   *fc_subst_location = NULL;
	int			fc_scan_offset = 0;
	char		fc_last_char;

	/* XXX */
	if (!*fc_fmt)
	{
		fc_end_position = fc_fmt;
		return fc_end_position;
	}

	/*
					 * XXX 是否使用 DecodeSpecial？这里需要 strcasecmp 吗？
					 */
	while (fc_fmt[fc_scan_offset] == '%' && fc_fmt[fc_scan_offset + 1])
	{
		/* XXX */
		fc_scan_offset += 2;
	}
	fc_next_percent = strchr(fc_fmt + fc_scan_offset, '%');
	if (fc_next_percent)
	{
		/* 测试是否 > 24:00:00 */

		fc_subst_location = fc_next_percent;
		while (*(fc_subst_location - 1) == ' ' && fc_subst_location - 1 > fc_fmt + fc_scan_offset)
			fc_subst_location--;
		fc_last_char = *fc_subst_location;
		*fc_subst_location = '\0';

		/* XXX: 1900 被编译为基年 */

		/*
		 * There is one special case. Imagine: str = " 2", fmt = "%d %...",
		 * since we want to allow blanks as "dynamic" padding we have to
		 * accept this. Now, we are called with a fmt of " %..." and look for
		 * " " in str. We find it at the first position and never read the
		 * 2...
		 */
		while (*fc_str == ' ')
			fc_str++;
		fc_end_position = strstr(fc_str, fc_fmt + fc_scan_offset);
		*fc_subst_location = fc_last_char;
	}
	else
	{
		/*
		 * there is no other percent sign. So everything up to the end has to
		 * match.
		 */
		fc_end_position = fc_str + strlen(fc_str);
	}
	if (!fc_end_position)
	{
		/*
		 * maybe we have the following case:
		 *
		 * str = "4:15am" fmt = "%M:%S %p"
		 *
		 * at this place we could have
		 *
		 * str = "15am" fmt = " %p"
		 *
		 * and have set fmt to " " because overwrote the % sign with a NULL
		 *
		 * In this case where we would have to match a space but can't find
		 * it, set end_position to the end of the string
		 */
		if ((fc_fmt + fc_scan_offset)[0] == ' ' && fc_fmt + fc_scan_offset + 1 == fc_subst_location)
			fc_end_position = fc_str + strlen(fc_str);
	}
	return fc_end_position;
}

static int fc_pgtypes_defmt_scan(union un_fmt_comb *fc_scan_val, int fc_scan_type, char **fc_pstr, char *fc_pfmt)
{
	/*
	 * scan everything between pstr and pstr_end. This is not including the
	 * last character so we might set it to '\0' for the parsing
	 */

	char		fc_last_char;
	int			fc_err = 0;
	char	   *fc_pstr_end;
	char	   *fc_strtol_end = NULL;

	while (**fc_pstr == ' ')
		fc_pstr++;
	fc_pstr_end = fc_find_end_token(*fc_pstr, fc_pfmt);
	if (!fc_pstr_end)
	{
		/* there was an error, no match */
		return 1;
	}
	fc_last_char = *fc_pstr_end;
	*fc_pstr_end = '\0';

	switch (fc_scan_type)
	{
		case PGTYPES_TYPE_UINT:

			/*
			 * numbers may be blank-padded, this is the only deviation from
			 * the fmt-string we accept
			 */
			while (**fc_pstr == ' ')
				(*fc_pstr)++;
			errno = 0;
			fc_scan_val->uint_val = (unsigned int) strtol(*fc_pstr, &fc_strtol_end, 10);
			if (errno)
				fc_err = 1;
			break;
		case PGTYPES_TYPE_UINT_LONG:
			while (**fc_pstr == ' ')
				(*fc_pstr)++;
			errno = 0;
			fc_scan_val->luint_val = (unsigned long int) strtol(*fc_pstr, &fc_strtol_end, 10);
			if (errno)
				fc_err = 1;
			break;
		case PGTYPES_TYPE_STRING_MALLOCED:
			fc_scan_val->str_val = pgtypes_strdup(*fc_pstr);
			if (fc_scan_val->str_val == NULL)
				fc_err = 1;
			break;
	}
	if (fc_strtol_end && *fc_strtol_end)
		*fc_pstr = fc_strtol_end;
	else
		*fc_pstr = fc_pstr_end;
	*fc_pstr_end = fc_last_char;
	return fc_err;
}

/* XXX range checking */
int PGTYPEStimestamp_defmt_scan(char **fc_str, char *fc_fmt, timestamp * fc_d,
							int *fc_year, int *fc_month, int *fc_day,
							int *fc_hour, int *fc_minute, int *fc_second,
							int *fc_tz)
{
	union un_fmt_comb fc_scan_val;
	int			fc_scan_type;

	char	   *fc_pstr,
			   *fc_pfmt,
			   *fc_tmp;
	int			fc_err = 1;
	unsigned int fc_j;
	struct tm	fc_tm;

	fc_pfmt = fc_fmt;
	fc_pstr = *fc_str;

	while (*fc_pfmt)
	{
		fc_err = 0;
		while (*fc_pfmt == ' ')
			fc_pfmt++;
		while (*fc_pstr == ' ')
			fc_pstr++;
		if (*fc_pfmt != '%')
		{
			if (*fc_pfmt == *fc_pstr)
			{
				fc_pfmt++;
				fc_pstr++;
			}
			else
			{
				/* Error: no match */
				fc_err = 1;
				return fc_err;
			}
			continue;
		}
		/* here *pfmt equals '%' */
		fc_pfmt++;
		switch (*fc_pfmt)
		{
			case 'a':
				fc_pfmt++;

				/*
				 * we parse the day and see if it is a week day but we do not
				 * check if the week day really matches the date
				 */
				fc_err = 1;
				fc_j = 0;
				while (pgtypes_date_weekdays_short[fc_j])
				{
					if (strncmp(pgtypes_date_weekdays_short[fc_j], fc_pstr,
								strlen(pgtypes_date_weekdays_short[fc_j])) == 0)
					{
						/* 找到了 */
						fc_err = 0;
						fc_pstr += strlen(pgtypes_date_weekdays_short[fc_j]);
						break;
					}
					fc_j++;
				}
				break;
			case 'A':
				/* see note above */
				fc_pfmt++;
				fc_err = 1;
				fc_j = 0;
				while (days[fc_j])
				{
					if (strncmp(days[fc_j], fc_pstr, strlen(days[fc_j])) == 0)
					{
						/* 找到了 */
						fc_err = 0;
						fc_pstr += strlen(days[fc_j]);
						break;
					}
					fc_j++;
				}
				break;
			case 'b':
			case 'h':
				fc_pfmt++;
				fc_err = 1;
				fc_j = 0;
				while (months[fc_j])
				{
					if (strncmp(months[fc_j], fc_pstr, strlen(months[fc_j])) == 0)
					{
						/* 找到了 */
						fc_err = 0;
						fc_pstr += strlen(months[fc_j]);
						*fc_month = fc_j + 1;
						break;
					}
					fc_j++;
				}
				break;
			case 'B':
				/* see note above */
				fc_pfmt++;
				fc_err = 1;
				fc_j = 0;
				while (pgtypes_date_months[fc_j])
				{
					if (strncmp(pgtypes_date_months[fc_j], fc_pstr, strlen(pgtypes_date_months[fc_j])) == 0)
					{
						/* 找到了 */
						fc_err = 0;
						fc_pstr += strlen(pgtypes_date_months[fc_j]);
						*fc_month = fc_j + 1;
						break;
					}
					fc_j++;
				}
				break;
			case 'c':
				/* XXX */
				break;
			case 'C':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				*fc_year = fc_scan_val.uint_val * 100;
				break;
			case 'd':
			case 'e':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				*fc_day = fc_scan_val.uint_val;
				break;
			case 'D':

				/*
				 * we have to concatenate the strings in order to be able to
				 * find the end of the substitution
				 */
				fc_pfmt++;
				fc_tmp = pgtypes_alloc(strlen("%m/%d/%y") + strlen(fc_pstr) + 1);
				strcpy(fc_tmp, "%m/%d/%y");
				strcat(fc_tmp, fc_pfmt);
				fc_err = PGTYPEStimestamp_defmt_scan(&fc_pstr, fc_tmp, fc_d, fc_year, fc_month, fc_day, fc_hour, fc_minute, fc_second, fc_tz);
				free(fc_tmp);
				return fc_err;
			case 'm':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				*fc_month = fc_scan_val.uint_val;
				break;
			case 'y':
			case 'g':			/* XXX difference to y (ISO) */
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				if (*fc_year < 0)
				{
					/* not yet set */
					*fc_year = fc_scan_val.uint_val;
				}
				else
					*fc_year += fc_scan_val.uint_val;
				if (*fc_year < 100)
					*fc_year += 1900;
				break;
			case 'G':
				/* XXX difference to %V (ISO) */
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				*fc_year = fc_scan_val.uint_val;
				break;
			case 'H':
			case 'I':
			case 'k':
			case 'l':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				*fc_hour += fc_scan_val.uint_val;
				break;
			case 'j':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);

				/*
				 * XXX what should we do with that? We could say that it's
				 * sufficient if we have the year and the day within the year
				 * to get at least a specific day.
				 */
				break;
			case 'M':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				*fc_minute = fc_scan_val.uint_val;
				break;
			case 'n':
				fc_pfmt++;
				if (*fc_pstr == '\n')
					fc_pstr++;
				else
					fc_err = 1;
				break;
			case 'p':
				fc_err = 1;
				fc_pfmt++;
				if (strncmp(fc_pstr, "am", 2) == 0)
				{
					*fc_hour += 0;
					fc_err = 0;
					fc_pstr += 2;
				}
				if (strncmp(fc_pstr, "a.m.", 4) == 0)
				{
					*fc_hour += 0;
					fc_err = 0;
					fc_pstr += 4;
				}
				if (strncmp(fc_pstr, "pm", 2) == 0)
				{
					*fc_hour += 12;
					fc_err = 0;
					fc_pstr += 2;
				}
				if (strncmp(fc_pstr, "p.m.", 4) == 0)
				{
					*fc_hour += 12;
					fc_err = 0;
					fc_pstr += 4;
				}
				break;
			case 'P':
				fc_err = 1;
				fc_pfmt++;
				if (strncmp(fc_pstr, "AM", 2) == 0)
				{
					*fc_hour += 0;
					fc_err = 0;
					fc_pstr += 2;
				}
				if (strncmp(fc_pstr, "A.M.", 4) == 0)
				{
					*fc_hour += 0;
					fc_err = 0;
					fc_pstr += 4;
				}
				if (strncmp(fc_pstr, "PM", 2) == 0)
				{
					*fc_hour += 12;
					fc_err = 0;
					fc_pstr += 2;
				}
				if (strncmp(fc_pstr, "P.M.", 4) == 0)
				{
					*fc_hour += 12;
					fc_err = 0;
					fc_pstr += 4;
				}
				break;
			case 'r':
				fc_pfmt++;
				fc_tmp = pgtypes_alloc(strlen("%I:%M:%S %p") + strlen(fc_pstr) + 1);
				strcpy(fc_tmp, "%I:%M:%S %p");
				strcat(fc_tmp, fc_pfmt);
				fc_err = PGTYPEStimestamp_defmt_scan(&fc_pstr, fc_tmp, fc_d, fc_year, fc_month, fc_day, fc_hour, fc_minute, fc_second, fc_tz);
				free(fc_tmp);
				return fc_err;
			case 'R':
				fc_pfmt++;
				fc_tmp = pgtypes_alloc(strlen("%H:%M") + strlen(fc_pstr) + 1);
				strcpy(fc_tmp, "%H:%M");
				strcat(fc_tmp, fc_pfmt);
				fc_err = PGTYPEStimestamp_defmt_scan(&fc_pstr, fc_tmp, fc_d, fc_year, fc_month, fc_day, fc_hour, fc_minute, fc_second, fc_tz);
				free(fc_tmp);
				return fc_err;
			case 's':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT_LONG;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				/* number of seconds in scan_val.luint_val */
				{
					struct tm  *fc_tms;
					time_t		fc_et = (time_t) fc_scan_val.luint_val;

					fc_tms = gmtime(&fc_et);

					if (fc_tms)
					{
						*fc_year = fc_tms->tm_year + 1900;
						*fc_month = fc_tms->tm_mon + 1;
						*fc_day = fc_tms->tm_mday;
						*fc_hour = fc_tms->tm_hour;
						*fc_minute = fc_tms->tm_min;
						*fc_second = fc_tms->tm_sec;
					}
					else
						fc_err = 1;
				}
				break;
			case 'S':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				*fc_second = fc_scan_val.uint_val;
				break;
			case 't':
				fc_pfmt++;
				if (*fc_pstr == '\t')
					fc_pstr++;
				else
					fc_err = 1;
				break;
			case 'T':
				fc_pfmt++;
				fc_tmp = pgtypes_alloc(strlen("%H:%M:%S") + strlen(fc_pstr) + 1);
				strcpy(fc_tmp, "%H:%M:%S");
				strcat(fc_tmp, fc_pfmt);
				fc_err = PGTYPEStimestamp_defmt_scan(&fc_pstr, fc_tmp, fc_d, fc_year, fc_month, fc_day, fc_hour, fc_minute, fc_second, fc_tz);
				free(fc_tmp);
				return fc_err;
			case 'u':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				if (fc_scan_val.uint_val < 1 || fc_scan_val.uint_val > 7)
					fc_err = 1;
				break;
			case 'U':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				if (fc_scan_val.uint_val > 53)
					fc_err = 1;
				break;
			case 'V':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				if (fc_scan_val.uint_val < 1 || fc_scan_val.uint_val > 53)
					fc_err = 1;
				break;
			case 'w':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				if (fc_scan_val.uint_val > 6)
					fc_err = 1;
				break;
			case 'W':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				if (fc_scan_val.uint_val > 53)
					fc_err = 1;
				break;
			case 'x':
			case 'X':
				/* XXX */
				break;
			case 'Y':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_UINT;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				*fc_year = fc_scan_val.uint_val;
				break;
			case 'z':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_STRING_MALLOCED;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				if (!fc_err)
				{
					fc_err = fc_DecodeTimezone(fc_scan_val.str_val, fc_tz);
					free(fc_scan_val.str_val);
				}
				break;
			case 'Z':
				fc_pfmt++;
				fc_scan_type = PGTYPES_TYPE_STRING_MALLOCED;
				fc_err = fc_pgtypes_defmt_scan(&fc_scan_val, fc_scan_type, &fc_pstr, fc_pfmt);
				if (!fc_err)
				{
					/*
					 * XXX use DecodeSpecial instead?  Do we need strcasecmp
					 * here?
					 */
					fc_err = 1;
					for (fc_j = 0; fc_j < szdatetktbl; fc_j++)
					{
						if ((datetktbl[fc_j].type == TZ || datetktbl[fc_j].type == DTZ) &&
							pg_strcasecmp(datetktbl[fc_j].token,
										  fc_scan_val.str_val) == 0)
						{
							*fc_tz = -datetktbl[fc_j].value;
							fc_err = 0;
							break;
						}
					}
					free(fc_scan_val.str_val);
				}
				break;
			case '+':
				/* XXX */
				break;
			case '%':
				fc_pfmt++;
				if (*fc_pstr == '%')
					fc_pstr++;
				else
					fc_err = 1;
				break;
			default:
				fc_err = 1;
		}
	}
	if (!fc_err)
	{
		if (*fc_second < 0)
			*fc_second = 0;
		if (*fc_minute < 0)
			*fc_minute = 0;
		if (*fc_hour < 0)
			*fc_hour = 0;
		if (*fc_day < 0)
		{
			fc_err = 1;
			*fc_day = 1;
		}
		if (*fc_month < 0)
		{
			fc_err = 1;
			*fc_month = 1;
		}
		if (*fc_year < 0)
		{
			fc_err = 1;
			*fc_year = 1970;
		}

		if (*fc_second > 59)
		{
			fc_err = 1;
			*fc_second = 0;
		}
		if (*fc_minute > 59)
		{
			fc_err = 1;
			*fc_minute = 0;
		}
		if (*fc_hour > 24 ||		/*
		 * 我们不想分配额外的内存，因此我们暂时将 '%' 符号设置为 '\0' 并调用 strstr。但是由于我们允许空白浮动在所有内容周围，我们必须缩短模式，直到我们达到一个非空白字符
		 */
			(*fc_hour == 24 && (*fc_minute > 0 || *fc_second > 0)))
		{
			fc_err = 1;
			*fc_hour = 0;
		}
		if (*fc_month > MONTHS_PER_YEAR)
		{
			fc_err = 1;
			*fc_month = 1;
		}
		if (*fc_day > day_tab[isleap(*fc_year)][*fc_month - 1])
		{
			*fc_day = day_tab[isleap(*fc_year)][*fc_month - 1];
			fc_err = 1;
		}

		fc_tm.tm_sec = *fc_second;
		fc_tm.tm_min = *fc_minute;
		fc_tm.tm_hour = *fc_hour;
		fc_tm.tm_mday = *fc_day;
		fc_tm.tm_mon = *fc_month;
		fc_tm.tm_year = *fc_year;

		tm2timestamp(&fc_tm, 0, fc_tz, fc_d);
	}
	return fc_err;
}

/* XXX: 1900 is compiled in as the base for years */
